共计 7893 个字符,预计需要花费 20 分钟才能阅读完成。
Promise 源码实现
前言
自从 ECMAScript 2015(es6)问世以来 javascript 就不仅仅是一个简略脚本语言了真正的成为了一种能够用于企业级开发的计算机高级程序设计语言,es6 新增了很多货色,能够算的上对整个 javascript 语言进行了重构。其中最重要的之一当属 promsie。此文章因为笔者程度无限,仍然存在很多有余。只是实现了 promise 的基本功能,也没有齐全依照 Promise/A+ 标准来实现。然而能够为咱们提供一个程序设计的思路,开辟咱们的思维。在浏览此文章之前心愿您对 promise 的根本用法有所理解,并且把握公布订阅设计模式,递归,以及函数式编程等思维。闲言少序让咱们开始吧!
代码实现
1. 根本架构
//1. 3 种状态 pending fulfilled rejected 三种状态
//2. 链式调用 then
//3.executon 执行器函数 这个函数是同步执行的
// 此时此刻 executon 函数是实际上是理论参数,咱们在实例化 promise 的时候传进去的 resolve 和 reject 才是模式 参数 : 重点
// 上述是 promise 的外围三大概念
// 咱们须要明确的一点是 new Promsie 肯定是通过构造函数或者 class 关键字去定义一个类,在这里咱们应用类 的模式去写
class MyPromise{
//executon 函数就是咱们实例化 promsie 传入的回调函数,作为主体函数
constructor(executon){
this.state = 'pending'// 这个用于保留咱们 promise 的三种状态 默认是 pending 即期待状态
this.value = undefined// 这是咱们最终胜利返回的一个 promise 执行后果的值
this.reason = undefined// 这是咱们执行谬误最终返回的一个值
// 在执行胜利的函数时候咱们首先能够做的就是扭转 promise 的状态, 接管一个参数, 并把这个 value 赋值给以后类的 value
let resolve = (value)=>{if(this.state === 'pending'){
this.state='fullFilled'
this.value = value
}
}
// 同理在执行失败的函数中咱们也要扭转 promise 的状态并对谬误进行赋值
let reject=(reason)=>{if(this.state === 'pending'){
this.state = 'rejected'
this.reason = reason
}
}
// 主体函数
executon(resolve,reject)
}
// 并且这两个参数都是回调函数,javascript 弱小之处体现的一点就在于反对函数式编程所谓的函数式编程感兴趣的同学能够上来钻研一下, 这里对应着就是 then 办法外面胜利的回调和失败的回调
then(onFullFilled,onRejected){
// 这里呢咱们也须要判断以后的状态
// 当咱们的状态变成胜利的时候咱们须要执行 then 外面胜利的回调函数
if(this.state === 'fullFilled'){onFullFilled(this.value)
}
// 同理当状态变成失败的时候咱们也要执行失败的回调函数,并把失败的值穿进去
if(this.state === 'rejected'){onRejected(this.reason)
}
}
}
1.1 测试用例 1
// 此时基础架构以及搭建好了咱们来试试
// 咱们能够在这里剖析一下整个执行过程
//1. 当咱们实例化这个 myPromise 的时候咱们应用了 resolve 去吧状态改成 fullFilled 所以咱们 then 外面胜利的 函数则会执行
//2.resolve 此时是一个形式参数来接管 executon 函数外部执行后果 executon 执行结束才会执行咱们的 resolv e 所有大家能够看到
//3. 只管 resolve 先执行 然而自身这是一个回调函数所以仍然是先打印 1 再打印 10 从一种不谨严的角度登程
// 这里能够了解成微工作
new MyPromise((resolve, reject) => {resolve(10)
conssole.log(1)//1
}).then((res) => {console.log(res)//10
})
上述代码以及测试用例都能通过了,下一步咱们将解决一下谬误捕捉,promise 外部呈现任何谬误都会被捕捉到,从而不会阻塞代码的执行,上面间接上代码
class MyPromise{constructor(executon){
this.state = 'pending'
this.value = undefined
this.reason = undefined
let resolve = (value)=>{if(this.state === 'pending'){
this.state='fullFilled'
this.value = value
}
}
let reject=(reason)=>{if(this.state === 'pending'){
this.state = 'rejected'
this.reason = reason
}
}
// 捕捉谬误次要是在这里捕捉
try {executon(resolve,reject)
} catch (error) {
// 抛出谬误
reject(error)
}
}
then(onFullFilled,onRejected){if(this.state === 'fullFilled'){onFullFilled(this.value)
}
if(this.state === 'rejected'){onRejected(this.reason)
}
}
}
1.2 测试用例 2
new MyPromise((resolve, reject) => {console.log(a)// 一个未定义的 a 必然出错
reject(10)
}).then((res) => {console.log(res)// 这里没有打印
}, (reason) => {console.log(reason, 'error')// 这里捕捉到了谬误
})
2 解决异步工作
当初咱们的 myPromise 曾经有了根本的架构,然而并不具备异步的解决能力,promise 的诞生就是为了解决 javascript 传统的应用回调函数去承受一个异步工作处理结果的缺点,这种缺点就是咱们陈词滥调的回调天堂。
那么 promise 外部到底是怎么做的能够防止陷入回调天堂呢?答案是采纳公布订阅者设计模式,这种设计模式的实质就是存在一种一对多的依赖关系,不理解这种设计模式的同学,能够查一查资料尝试写一写。上面咱们间接上代码
class MyPromise{constructor(executon){
this.state = 'pending'
this.value = undefined
this.reason = undefined
// 利用公布订阅设计模式在适合的机会去执行注册的回调函数即在异步的回调函数执行实现咱们再执行 then 外面的代码
// 既然是公布订阅那么咱们则须要两个缓存列表 (缓存对应的回调函数) 一个胜利的列表 一个失败的列表
this.onResolveCallback = []// 胜利
this.onRejectedCallback = []// 失败
let resolve = (value)=>{if(this.state === 'pending'){
this.state='fullFilled'
this.value = value
// 重写 resolve 办法咱们须要在适合的机会去执行异步对应的回调函数
// 这里能够了解为公布订阅外面的订阅者
this.onResolveCallback.forEach(fn=>{fn()})
}
}
let reject=(reason)=>{if(this.state === 'pending'){
this.state = 'rejected'
this.reason = reason
// 同理 reject 也是须要去执行对应的函数
this.onRejectedCallback.forEach(fn=>{fn()})
}
}
try {executon(resolve,reject)
} catch (error) {reject(error)
}
}
then(onFullFilled,onRejected){if(this.state === 'fullFilled'){onFullFilled(this.value)
}
if(this.state === 'rejected'){onRejected(this.reason)
}
// 如果咱们在实例化的时候向 executon 函数加上一个异步工作那么此时咱们的状态将永远是期待状态
// 因为,异步会后于同步代码执行,咱们的胜利回调也就是 resolve 还没执行,then 办法外面的判断胜利也不会 执行,那么状态将永远是期待状态所有咱们须要在这里判断一下如果呈现异步的状况,那么咱们的状态将永远 是期待状态
if(this.state === 'pending'){
// 如果是期待状态咱们须要向咱们缓存列表外面注册胜利回调函数,在将来的某个时刻去执行他们
// 这里也能够了解为公布带订阅外面的发布者
this.onResolveCallback.push(()=>{onFullFilled(this.value)// 传入胜利的
})
// 同理如果是失败的状况也要注册一个失败的回调函数
this.onRejectedCallback.push(()=>{onRejected(this.reason)
})
}
}
}
2.1 测试用例 3 上述代码为咱们的 myPromise 增加了解决异步工作的能力,那么就要补上咱们的测试
let p1 = new MyPromise((resolve, reject) => {
// 请留神 setTimeout 家喻户晓是异步的而且是一个宏工作是咱们的的浏览器提供的 api。setTimeout 会在所有的同步代码后执行
// 我么能够剖析一下此时代码的执行过程,当咱们实例化的时候遇到了异步,那么咱们状态就不会失去扭转,这时咱们在 myPromise 的 then 办法外面回去注册胜利的回调函数,当 1s 过后咱们的 resolve 函数执行,那么状态就会产生扭转,此时咱们缓存列表对应的回调也会执行,这里十分绕,很难了解,手动写一写将会有助于了解
setTimeout(() => {resolve(100)
},1000)
})
p1.then((res) => {console.log(res + 1)//1s 后 打印 101 测试通过
})
2.2 测试用例 4 因为咱们应用了缓存列表所有每一个 then 都能捕捉到异步都执行同样的,咱们也补上测试用例
let p1 = new MyPromise((resolve, reject) => {setTimeout(() => {resolve(100)
}.1000)
})
p1.then((res) => {console.log(res + 1)//1s 打印 101
})
p1.then((res) => {console.log(res + 2)//1s 后 打印 102
})
3. 实现链式调用
当初最简单,最绕,最硬核,最重要的链式调用要来了,链式调用是 promsie 的外围,重中之重。这一段十分难以了解,笔者写的也不是很分明,还请多多包涵,如果想要弄得这里还是那句话得入手写一写。上代码
class MyPromise {constructor(executon) {
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onResolveCallback = []
this.onRejectedCallback = []
let resolve = (value) => {if (this.state === 'pending') {
this.state = 'fullFilled'
this.value = value
this.onResolveCallback.forEach(fn => { fn() })
}
}
let reject = (reason) => {if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.onRejectedCallback.forEach(fn => { fn() })
}
}
try {executon(resolve, reject)
} catch (error) {reject(error)
}
}
// 链式调用 : 重点
// 这里实际上也是接管的形式参数 onFullFilled onRejected
// 并且这两个参数都是回调函数,javascript 弱小之处体现的一点就在于反对函数式编程所谓的函数式编程感兴趣的同学能够上来钻研一下
then(onFullFilled, onRejected) {
// 实现链式调用咱们的 then 外面要默认返回一个新的 promsie
// 留神思考一下这里为什么不能间接 return this? 因为这里的 this 实际上是以后的 proise 实例
// 所以要返回一个新的 promsie 这里十分的绕啊 我给出论断是 咱们 then 多少次那么这里的 代码就 会执行多少次
// 所以这里实际上是一个递归
// 并且我门须要解决如果 promise 实例外面的 then 也返回的是一个新的 promise 实例应该怎么做?// 实质上就是依据 x 的值来解决不同的后果 定义一个 resolvePromsie 函数来执行下一步
let p2 = new MyPromise((resolve, reject) => {
let x;// 记录一下 咱们能够想一想这个 x 到底是什么货色?其实就是一个用于记录 then 外面返回的后果,十分难以了解啊这里
// 这里呢咱们也须要判断以后的状态
if (this.state === 'fullFilled') {
// 并且传入以后的 value 值,很重要的一点是 此时的 this 指向的 myclass 这个类即指向结构函 数,这里的 this.value 是一个理论参数
// 并且这里肯定要走异步否自状态永远是 pending 状态因为 then 外面也可能是实例化了一个新 的 promise 并且在外面做了一个异步工作
setTimeout(()=>{x = onFullFilled(this.value)
resolvePromsie(p2,x,resolve,reject)
})
}
if (this.state === 'rejected') {x=onRejected(this.reason)
resolvePromsie(p2,x,resolve,reject)
}
if (this.state === 'pending') {this.onResolveCallback.push(() => {x=onFullFilled(this.value)
resolvePromsie(p2,x,resolve,reject)
})
this.onRejectedCallback.push(() => {x=onRejected(this.reason)
resolvePromsie(p2,x,resolve,reject)
})
}
})
//return 这个实例进来
return p2
}
}
// 定义一个执行下一步操作的函数这里次要是接管了四个参数
//p2 一个 promise 的实例,在标准外面 then 办法会默认返回一个新的 promise 实例 留神这里是新的
function resolvePromsie(p2,x,resolve,reject){
// 请留神 p2 和 x 不能是同一个值至于为什么能够思考一下 或者依据 promise A+ 规范去了解
if(x === p2){return new Error('援用谬误')
}
// 为了兼容其余 promise 的库咱们这里做了一些兼容 x 能够是一个新的实例也能够是一个函数
if(typeof x === 'object' && x!== null || typeof x=== 'function'){
// 谬误捕捉
try {
// x 如果是一个新的 promise 实例那么它必然有 then 办法
let then = x.then
if(typeof then === 'function'){
// 如果存在 then 间接让 x 调用
then.call(x,y=>{
// 胜利的回调
//resolve(y) 留神这里如果仅仅是调用 resolve(y)那么只是判断了第二层的回调函数第二层往下便执行不了
// 所以这里必须要递归执行 resolvePromsie 无论有多少 promise 嵌套多少实例都能监听到
resolvePromsie(p2,y,resolve,reject)
},err=>{
// 失败的回调 失败就间接抛出谬误无需递归
reject(err)
})
}
} catch (error) {reject(err)
}
}else{
// 如果 x 是一个一般值则间接调用胜利即可
resolve(x)
}
}
3.1 测试用例 5 上述代码曾经为咱们补上了 链式调用曾经如果咱们在 then 外面从新实例化一个 promse 的处理结果 批准咱们补上测试用例
let p1 = new MyPromise((resolve, reject) => {resolve(100)
})
p1.then((res) => {console.log(res + 1)//101
return res+4
}).then((res)=>{console.log(res)//102
})
3.2 测试用例 6 异步链式调用
let p1 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(100)
})
})
p1.then((res) => {console.log(res + 1)//101
return res+4
}).then((res)=>{console.log(res)//104
})
3.3 测试用例 7 then 外面返回一个新的 promise 实例
let p1 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(100)
})
})
p1.then((res) => {console.log(res)//100
return new MyPromise((resolve,reject)=>{resolve(200)
})
}).then((res)=>{console.log(res)//200
})
3.4 测试用例 8 程序执行异步工作 这也是 promise 的外围 即解决回调天堂
let p1 = new MyPromise((resolve, reject) => {setTimeout(()=>{console.log(1)// 3s 后打印 1
resolve()},3000)
})
p1.then((res) => {return new MyPromise((resolve,reject)=>{setTimeout(()=>{console.log(2)// 函数体外面的异步执行实现后 等 2s 打印 2
resolve()},2000)
})
}).then((res)=>{console.log(3)// 最初是 3
})
// 这里是整个 promise 的外围 同步的形式去执行异步工作
结语
手写一个 promise 并非一件很容易的事,此案例还有很多办法没有实现,感兴趣的同学能够再次根底之上实现一下 all,race,finally 等办法,程序自身就是一件很形象的事,只能在一次又一次的实际中锤炼本人的思维。作者实力无限,还望斧正。