不知道你是不是像我一样,看过无数个 Promise 实现,难读难懂,即使背下来了,面试时候链式调用也很难写出来,问题就出在你没有理解 promise 状态变更!本文手摸手带你实现一个面试官无可挑剔的 Promise,理解底层逻辑,其实很简单!
实现 Promise 思路
实现 Promise 的两大部分:
-
状态变更回调
状态变更后调用从 then 或者 catch 收集来的回调
-
链式调用
状态传递、错误冒泡、返回值为 promise 处理等
状态变更回调
这一部分很简单,其实就是一个 EventEmitter,只不过事件只有 resolve 和 reject 且只能调用一次 emit。
首先 Promise 有三种状态 pendding、fulfilled、rejected 且只能改变一次状态,所以我们很自然地写下下面代码
class MyPromise {
state = 'pendding' // 'pendding' | 'fulfilled' | 'rejected'
value = undefined // 用于保存 executor 执行成功的返回值
reason = undefined // 用于保存 executor 执行措辞的信息
constructor(executor) {
try {
executor(this.resolve.bind(this),this.reject.bind(this))
} catch (e) {
this.reject(e)
}
}
resolve(value) {
if (this.state === 'pendding') {
this.state = 'fulfilled'
this.value = value
}
}
reject(reason) {
if (this.state === 'pendding') {
this.state = 'rejected'
this.reason = reason
}
}
}
const p1 = new MyPromise((r)=>setTimeout(r,1000)) // 一秒后 p1.state 变为 fulfilled
这里我 resolve 和 reject 放在原型上然后用 bind 绑定,方便代码的拆分和阅读。
Promise 构造函数创造的 promise 状态改变有两种方式:
- 传入 excutor 中,由内部调用 reject 或者 resolve
- excutor 出错,调用 reject
excutor 要放入 try-catch 中,若是出错可直接执行 reject
接着我们实现 then 和 catch,需要两个数组收集 callback
class Promise {
// ...
+ onResolvedCallbacks = []
+ onRejectedCallbacks = []
constructor(executor) {
// ...
}
// ...
resolve(value) {
if (this.state === 'pendding') {
this.state = 'fulfilled'
this.value = value
// 执行对应事件回调
+ for (const onFulfilled of this.onResolvedCallbacks) onFulfilled(value)
}
}
reject(reason) {
if (this.state === 'pendding') {
this.state = 'rejected'
this.reason = reason
+ for (const onRejected of this.onRejectedCallbacks) onRejected(reason)
}
}
}
then 和 catch
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') onFulfilled = value => value
if (typeof onRejected !== 'function')
onRejected = reason => {
throw reason
}
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
catch(onRejected) {
return this.then(null, onRejected)
}
这其中有个很重要的操作,就是参数非函数类型时的默认值,按照 promise 规则,onFulfilled 非函数会将值直接传递,而onRejected = reason => { throw reason } 是错误冒泡的原理
测试
function valueLog(value){ console.log('ok:' + value) }
function catchLog(reason){ console.log('catch:' + reason) }
new MyPromise((r)=>setTimeout(()=>r(1),1000)).then(valueLog,catchLog) // ok:1
new MyPromise((_,r)=>setTimeout(()=>r(1),1000)).then(valueLog,catchLog) // catch:1
new MyPromise((_,r)=>setTimeout(()=>r(1),1000)).catch(catchLog) // catch:1
现在我们的回调是直接执行的,promise 的回调是在微任务中,这里直接用 promise.then 模拟了
const micTrigger = cb => Promise.resolve().then(cb) // 微任务触发器
如果你实在觉得 Promise 实现 Promise 别扭,你可以试试 MutationObserver,Vue 就是这么做的
class MicTaskTrigger { constructor(callback = () => {}) { this.counter = 1 this.node = document.createTextNode(String(this.counter)) this.callback = callback this.observer = new MutationObserver(() => { this.callback() }) this.observer.observe(this.node, { characterData: true }) } changeCallback(callback) { this.callback = callback } trigger(callback = () => {}) { this.callback = callback this.counter = (this.counter + 1) % 2 this.node.data = String(this.counter) } } const micTaskTrigger = new MicTaskTrigger() // 同样也是微任务触发器
resolve(value) {
if (this.state === 'pendding') {
this.state = 'fulfilled'
this.value = value
- for (const onFulfilled of this.onResolvedCallbacks) onFulfilled(value)
+ for (const onFulfilled of this.onResolvedCallbacks) micTrigger(() => onFulfilled(value))
}
}
reject(reason) {
if (this.state === 'pendding') {
this.state = 'rejected'
this.reason = reason
- for (const onRejected of this.onRejectedCallbacks) onRejected(reason)
+ for (const onRejected of this.onRejectedCallbacks) micTrigger(() => onRejected(reason))
}
// 顺便加上两个静态方法以便测试
+ static resolve(value) {
+ return new MyPromise(r => r(value))
+ }
+ static reject(reason) {
+ return new MyPromise((_, r) => r(reason))
+ }
}
现在还有一个问题,若 promise 变为 fulfilled 状态了,又调用 promise.then 怎么办 ?且 new MyPromise(()=>{ throw new Error() }).then(valueLog,catchLog) 这个用例通过不了(本质都是 then 执行之前就变更了状态)
所以变更状态的 promise 的 then 或者 catch 需要直接执行回调
then(onFulfilled, onRejected){
// ...
if (this.state === 'pendding') {
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
} else if (this.state === 'fulfilled') { //直接执行
micTrigger(() => onFulfilled(this.value))
} else {
micTrigger(() => onRejected(this.reason))
}
}
测试
new MyPromise(()=>{ throw new Error() }).then(valueLog,catchLog) // catch:Error
// 测试微任务
console.log('start')
setTimeout(() => {
console.log('time')
})
MyPromise.resolve().then(() => {
console.log('resolve')
})
console.log('end')
// start end resolve time 符合顺序
一个简单的 Promise 大功告成!它可以用 then 收集回调,异步执行回调,也可也在状态变更后调用 then。然而还不够,毕竟链式调用才是 promise 的精髓。
链式调用
谁在操控状态?
在实现链式调用前,我们需要理清楚一件事情,promise 的状态是谁在操控?
显然,由 new Promise 创建的 promise1 是由 fn1 操控的,操控的过程是:由它内部调用 resolve 或者 reject 来改变 promise 的状态,如果执行过程出错就帮它 reject。那 then 和 catch 返回的 promise 呢?显然是传入 then 中的函数,比如 promise3 就由 fn3 或者 fn5 操控,promise1 是 resolved 就执行 fn3,否则 fn5, 所以,一个 promise 状态映射一次函数执行状态,接下来我们要做的就是实现将【函数执行状态映射为 promise 的状态】的操控过程,具体而言与 contructor 中对 excutor 做的事情差不多:执行成功后resolve(结果)与出错时reject(错误信息)。
class MyPromise {
// ...
handleCallback(callback, resolve, reject, param) { // 用于包装回调,实现操控过程
try {
const result = callback(param)
resolve(result)
} catch (e) {
reject(e)
}
}
// ...
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') onFulfilled = value => value
if (typeof onRejected !== 'function')
onRejected = reason => {
throw reason
}
// 原先的过程放入构造函数中以便拿到 resolve 和 reject 实现控制
+ const promise = new MyPromise((resolve, reject) => {
if (this.state === 'pendding') {
- this.onResolvedCallbacks.push(onFulfilled)
- this.onRejectedCallbacks.push(onRejected)
+ this.onResolvedCallbacks.push(this.handleCallback.bind(this, onFulfilled, resolve, reject))
+ this.onRejectedCallbacks.push(this.handleCallback.bind(this, onRejected, resolve, reject))
} else if (this.state === 'fulfilled') {
- micTrigger(() => onFulfilled(this.value))
+ micTrigger(() => this.handleCallback(onFulfilled, resolve, reject, this.value))
} else {
- micTrigger(() => onRejected(this.reason))
+ micTrigger(() => this.handleCallback(onRejected, resolve, reject, this.reason))
}
+ })
+ return promise
}
其实,你已经可以实现链式调用了!是不是很简单!
简单测试
下面是从 要就来45道Promise面试题一次爽到底 测试多个用例都通过了,下面是随便地截出的用例
let promise = new MyPromise((resolve, reject) => {
reject("error");
resolve("success2");
});
promise
.then(res => {
console.log("then1: ", res);
}).then(res => {
console.log("then2: ", res);
}).catch(err => {
console.log("catch: ", err);
}).then(res => {
console.log("then3: ", res);
})
// catch: error
// then3: undefined
// 错误冒泡
MyPromise.resolve().then(() => {
return new Error('error!!!')
}).then(res => {
console.log("then: ", res)
}).catch(err => {
console.log("catch: ", err)
})
// then: Error: error!!!
甚至在 async 里也都可以用
async function async1 () {
console.log('async1 start');
await new MyPromise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
'script start'
'async1 start'
'promise1'
'script end'
'promise1 resolve'
'async1 success'
'async1 end'
callback 返回 Promise
不过我们还需要处理一个情况,按照 Promise 规定,result 返回 promise 时,后面的函数就不受这个 callback 控制了,而是继承返回的 promise 状态。其实很简单,将 resolve, reject 传入 result.then 就可以将状态交由 result 托管了
handleCallback(callback, resolve, reject, param) { // 用于包装函数,实现操控过程
try {
const result = callback(param)
+ if (result instanceof MyPromise) result.then(resolve, reject)
+ else resolve(result)
- resolve(result)
} catch (e) {
reject(e)
}
}
错误冒泡
等等等等下!我们刚刚好像稀里糊涂地实现了错误冒泡机制,“为什么它能跑?”
实现代码:
if (typeof onRejected !== 'function')
onRejected = reason => {
throw reason
}
当不传入 onRejected 函数时,默认是一个直接抛出错误的函数,再配合 try-catch
handleCallback(callback, resolve, reject, param) {
try {
const result = callback(param)
if (result instanceof MyPromise) result.then(resolve, reject)
else resolve(result)
} catch (e) {
reject(e)
}
}
所以只要没有自定义的 onRejected 函数,这个错误就会被不断地抛出然后被捕获 reject,逐层向后冒泡,是不是很巧妙呢?
完整代码
不到六十行,而且其中还有很多模板代码。
const micTrigger = cb => Promise.resolve().then(cb)
class MyPromise {
state = 'pendding'; // 'pendding' | 'fulfilled' | 'rejected'
value = undefined; // 用于保存 executor 执行成功的返回值
reason = undefined; // 用于保存 executor 执行措辞的信息
onResolvedCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
this.reject(e)
}
}
resolve(value) {
if (this.state === 'pendding') {
this.state = 'fulfilled'
this.value = value
for (const onFulfilled of this.onResolvedCallbacks) micTrigger(() => onFulfilled(value))
}
}
reject(reason) {
if (this.state === 'pendding') {
this.state = 'rejected'
this.reason = reason
for (const onRejected of this.onRejectedCallbacks) micTrigger(() => onRejected(reason))
}
}
handleCallback(callback, resolve, reject, param) {
try {
const result = callback(param)
if (result instanceof MyPromise) result.then(resolve, reject)
else resolve(result)
} catch (e) {
reject(e)
}
}
then(onFulfilled, onRejected) {
if (typeof onFulfilled !== 'function') onFulfilled = value => value
if (typeof onRejected !== 'function')
onRejected = reason => {
throw reason
}
const promise = new MyPromise((resolve, reject) => {
if (this.state === 'pendding') {
this.onResolvedCallbacks.push(this.handleCallback.bind(this, onFulfilled, resolve, reject))
this.onRejectedCallbacks.push(this.handleCallback.bind(this, onRejected, resolve, reject))
} else if (this.state === 'fulfilled') {
micTrigger(() => this.handleCallback(onFulfilled, resolve, reject, this.value))
} else {
micTrigger(() => this.handleCallback(onRejected, resolve, reject, this.reason))
}
})
return promise
}
catch(onRejected) {
return this.then(null, onRejected)
}
static resolve(value) {
return new MyPromise(r => r(value))
}
static reject(reason) {
return new MyPromise((_, r) => r(reason))
}
}
小结
实现 Promise 就是实现一个一次性的 eventEmitter,以及理解 promise 状态是函数执行状态的映射,只需要将完成映射操控就可以实现链式调用了。
感谢阅读,欢迎评论~