很多手写 Promise 的文章喜欢直接罗列要点,一步到位实现完整代码,虽全面但不够友好,初学者往往难以消化。为此,我选择记录自己的理解,以循序渐进的方式,一步步实现 Promise,并详细讲解每个环节的设计原因,帮助大家轻松上手。
本文重点在于如何手动实现 Promise,关于其使用场景和意义,这里不再赘述。希望通过这篇文章,你能深入理解 Promise 的实现机制。
我们在开发过程中免不了使用一些异步获取数据相关的操作,下面将以最简单的一个模拟获取网络请求的例子作为切入点:
function request(fn) {
setTimeout(() => {
fn('success')
}, 1000)
}
request((res) => { // 把回调函数作为参数传给reuest方法
console.log(res); // 一秒后打印了success
})
我们把上面的回调函数的形式改成Promise的形式:
function requestFactory() {
return new Promise(resolve => {
setTimeout(() => {
resolve('success')
}, 1000)
})
}
const request = requestFactory()
request.then(res => {
console.log(res) // 一秒后也打印了success
})
如何实现上述Promise操作呢,不难发现then方法是Promise内部执行的,我们只需要把这个方法缓存起来就可以了
class IPromise {
constructor(executor) {
this.onResolveCallback = null // 用于缓存then传进来的方法
executor(this.resolve)
}
resolve = (value) => { // value => 传进来的success
this.onResolveCallback(value)
}
then = (onFulfilled) => {
this.onResolveCallback = onFulfilled
}
}
function requestFactory() {
return new IPromise(resolve => { // 这个回调函数作为executor传入IPromise
setTimeout(() => {
resolve('success')
}, 1000)
})
}
request.then(res => {
console.log(res) // 一秒后打印了success
})
好了,最简单的Promise已经实现了,根据Promise的特性,then是可以多次调用的,咱们这种实现形式会有一个问题
const request = requestFactory()
request.then(res => {
console.log('res1', res) // 被覆盖了
})
request.then(res => {
console.log('res2', res) // 1秒后打印了 res2 success
})
这是后面注册的回调函数把前面的覆盖掉了,这时候我们就可以使用数组来存储这些回调函数
class IPromise {
constructor(excutor) {
this.onResolveCallbacks = [] // 使用数组来保存回调函数
excutor(this.resolve)
}
resolve = (value) => {
this.onResolveCallbacks.forEach(resolve => resolve(value)) // 遍历调用保存的回调
}
then = (onFulfilled) => {
this.onResolveCallbacks.push(onFulfilled)
}
}
同理,reject注册的回调函数也可以这样处理,不过还需要加入一个捕获错误
class IPromise {
constructor(excutor) {
this.onResolveCallbacks = [] // 成功的回调
this.onRejectCallbacks = [] // 失败的回调
try {
excutor(this.resolve, this.reject) // 这里只能捕获同步错误
} catch (error) {
this.reject(error)
}
}
resolve = (value) => {
queueMicrotask(() => { // 需要放到微任务队列里面
this.onResolveCallbacks.forEach(resolve => resolve(value))
})
}
reject = (reason) => {
queueMicrotask(() => { // 不放到微任务队列里的话,错误都捕获不到
this.onRejectCallbacks.forEach(reject => reject(reason))
})
}
then = (onFulfilled, onRejected) => {
// 防止参数传空的情况
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
this.onResolveCallbacks.push(onFulfilled)
this.onRejectCallbacks.push(onRejected)
}
}
function requestFactory() {
return new IPromise((res, rej) => {
// throw new Error('error') 这样抛的错,reject需要放到异步去执行
setTimeout(() => {
rej(new Error('error')) // 试了下像上面的直接抛错,捕获不了,需要显式执行reject才行
}, 1000)
})
}
const request = requestFactory()
request.then((res) => {
console.log(res)
}, (error) => {
console.log('error', error);
})
代码越来越多了,贪多嚼不烂,下一章说一说怎么做到链式调用。