
手撕 Promise
字节前端实习一面就让我手写 Promise,没写出直接挂了 💔,亡羊补牢,为时不晚。
使用方法回顾
作为 ES6 提出来的新语法,首先回顾一下 Promise 的使用方法。
const promise = new Promise((reslove, reject) => {
reslove(233)
})
promise.then((value) => {
console.log(value) // 233
})
简单来说就是实例化一个 Promise
对象,实例化的时候传一个函数进去,这个函数有两个变量:
resolve
,是一个回调函数,用于处理成功的数据reject
,也是一个回调函数,用于处理失败的信息
简易版 Promise
简易版 Promise 🍣 呈上:
const PENDING = 'pending'
const FULLFILLED = 'resolved'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
// 实例化的时候立刻执行传入的这个参数
executor(this.resolve, this.reject)
}
// 初始化状态为 pending
status = PENDING
// value 存储成功的结果,reason 存储失败的结果
value = null
reason = null
resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULLFILLED
this.value = value
}
}
reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
}
}
then(onFullfilled, onRejected) {
if (this.status === FULLFILLED) {
// 如果传入的实例的状态是成功,则执行成功回调
onFullfilled(this.value)
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
}
}
}
测试成功:
const promise = new MyPromise((reslove, rejectct) => {
reslove(233)
})
promise.then((value) => {
console.log(value) // 233
})
然而这样子的代码是无法实现异步操作的,因为我们的 then
方法是在主线程中直接运行的,不会受到 executor
(也就是新建 Promise 对象的时候传的函数)里异步操作的限制。
异步操作扩展
因此,为了允许异步操作,我们要进行以下扩展:
// 存储成功回调函数
onFulfilledCallback = null
// 存储失败回调函数
onRejectedCallback = null
resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
this.onFulfilledCallback && this.onFulfilledCallback(value)
}
}
reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
this.onRejectedCallback && this.onRejectedCallback(reason)
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback = onFulfilled
this.onRejectedCallback = onRejected
}
}
但是这个方式有个很明显的缺陷,就是我们的 promise
一旦有多个 then
,它的原先的回调函数就会被新的回调函数覆盖掉。
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
promise.then((value) => {
console.log(1)
console.log(value)
})
promise.then((value) => {
console.log(2)
console.log(value)
})
promise.then((value) => {
console.log(3)
console.log(value)
})
// 3
// success
很容易就能想到,用数组来存储所有的成功回调和失败回调函数即可。
// 存储成功回调函数
onFulfilledCallback = []
// 存储失败回调函数
onRejectedCallback = []
resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
while (this.onFulfilledCallback.length) {
// 这里用 shift() 保证了每个成功回调只会运行一次
this.onFulfilledCallback.shift()(value)
}
}
}
reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(value)
}
}
}
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
// 如果传入的实例的状态是成功,则执行成功回调
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
}
链式调用
我们一鼓作气,再把 then
的链式调用给实现了。
逻辑也很清晰,就是在调用 then
的时候,我们需要判断一下,执行成功回调函数的返回值是否是一个 MyPromise
对象,两种情况:
- 是
MyPromise
对象,就继续调用返回的这个对象的then
方法 - 是普通值,就直接调用成功的回调函数
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
return new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 获取成功回调的结果
const ret = onFulfilled(this.value)
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
})
}
测试结果:
promise
.then((value) => {
console.log(1)
console.log(value)
return new MyPromise((resolve) => resolve('success'))
})
.then((value) => {
console.log(2)
console.log(value)
return new MyPromise((resolve) => resolve('success'))
})
.then((value) => {
console.log(3)
console.log(value)
})
// 1
// success
// 2
// success
// 3
// success
但是,这个实现还有很多限制,我们逐步解决。
循环调用
按我们上面的写法,我们如果在 promise.then
里面返回了同一个 promise
,结果会报 Uncaught ReferenceError
的错误。
const promise = new MyPromise((resolve, reject) => {
resolve('success')
})
const p = promise.then((value) => {
console.log(value)
return p
})
但是,官方的 Promise 报的却是 Type Error
的错误。
const promise = new Promise((resolve, reject) => {
resolve('success')
})
const p = promise.then((value) => {
console.log(value)
return p
})
该怎么还原呢,我们尝试修改 then
方法。
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
onRejected(this.reason)
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.pop(onRejected)
}
})
return p
}
在这里,还记得之前为了实现链式调用而新建了 MyPromise
对象,当时是直接 return
了这个对象,现在我们对其命名为 p
,并且保存下来,以便后续判断循环调用。
只需要在获取成功回调返回值的时候,判断其是否和我们新建的 p
相同
由于 p
是 New
完之后才能获得的一个对象,所以直接在原本的 then
函数中增加判重逻辑即可。
验证:
const promise = new MyPromise((resolve, reject) => {
resolve('new success')
})
// 定义一个循环调用的 promise
const p = promise.then((value) => {
console.log(1)
console.log(value)
return p
})
p.then(
(value) => {
console.log(2)
console.log(value)
},
(reason) => {
console.log(3)
console.log(reason)
}
)
捕获错误
在 Promise 中,为了避免发生错误就中断代码运行,我们需要对两个地方的错误进行捕获。
-
在实例化
MyPromise
的时候 -
在
then
的过程中
针对实例化的错误肯定是在构造函数中进行捕捉,实际上只需要对 executor
执行的增加异常捕获操作。
constructor(executor) {
try {
// 实例化的时候立刻执行传入的这个参数
executor(this.resolve, this.reject)
} catch (error) {
// 捕获错误并执行失败回调
this.reject(error)
}
}
针对 then
方法中出现的错误,显然就是处理对其进行异常捕获啦。
...
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
} catch (error) {
reject(error)
}
})
...
让我们来看看效果是否和我们预期一样。
构造函数中捕获错误:
const promise = new MyPromise((resolve, reject) => {
throw new Error('new failed')
resolve('success')
})
then
中捕获错误:
const promise = new MyPromise((resolve, reject) => {
resolve('new success')
})
promise
.then(
(value) => {
console.log(1, value)
throw new Error('then failed')
},
(reason) => {
console.log(2, reason)
}
)
.then(
(value) => {
console.log(3, value)
},
(reason) => {
console.log(4, reason)
}
)
优化 then 中的 REJECTED 和 PENDING 状态
先前我们对 then
方法中 FULFLLED
状态的操作进行了优化,包括:
- 创建微任务,实现检测循环调用错误
- 执行回调时增加捕获异常功能
现在我们也对 REJECTED
和 PENDING
状态进行以上补充。
首先为了书写方便,先定义一个处理 promise 返回值的函数:
function resolvePromise(p, ret, resolve, reject) {
if (p === ret) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (ret instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
ret.then(resolve, reject)
} else {
// ret 是普通值,则直接调用成功回调
resolve(ret)
}
}
修改 then
方法:
then(onFulfilled, onRejected) {
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const ret = onFulfilled(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
queueMicrotask(() => {
try {
const ret = onRejected(this.reason)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onFulfilled(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onRejected(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})
return p
}
then 方法中的默认函数
在调用 then
方法的时候,应该允许不写成功和失败的回调。
then(onFulfilled, onRejected) {
// 未指定成功和失败回调时,使用默认函数
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
onRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
...
}
resolve 和 reject 的静态调用
原生的 Promise 是支持无需实例化,直接调用 Promise.resolve()
来返回一个 Promise
对象的。
如果我们尝试用自定义的 MyPromise
进行这种操作:
MyPromise.resolve()
.then(() => {
console.log('1')
return MyPromise.resolve('success')
})
.then((value) => {
console.log(2, value)
})
报错提示如下:
因为是需要调用类方法,所以我们需要用 static
关键字声明 resolve
和 reject
的静态方法。
class MyPromise {
...
// resolve 静态方法
static resolve(parameter) {
if (parameter instanceof MyPromise) {
return parameter
}
return new MyPromise((resolve) => {
resolve(parameter)
})
}
// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
测试结果:
代码优化
对声明类时的冗余代码进行优化,优化完代码如下:
const PENDING = 'pending'
const FULFILLED = 'resolved'
const REJECTED = 'rejected'
class MyPromise {
constructor(executor) {
try {
// 实例化的时候立刻执行传入的这个执行器,并传入 resolve 和 reject
executor(this.resolve, this.reject)
} catch (error) {
// 捕获错误并执行失败回调
this.reject(error)
}
}
// 初始化状态为 pending
status = PENDING
// value 存储成功的结果,reason 存储失败的结果
value = null
reason = null
// 存储成功回调函数
onFulfilledCallback = []
// 存储失败回调函数
onRejectedCallback = []
resolve = (value) => {
// 只有当状态为 pending 的时候才执行成功回调
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 如果存在成功回调,就调用
while (this.onFulfilledCallback.length) {
this.onFulfilledCallback.shift()(value)
}
}
}
reject = (reason) => {
// 只有当状态为 pending 的时候才执行失败回调
if (this.status === PENDING) {
this.status = REJECTED
this.reason = reason
// 如果存在成失败回调,就调用
while (this.onRejectedCallback.length) {
this.onRejectedCallback.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
// 未指定成功和失败回调时,使用默认函数
const realOnFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : (value) => value
const realOnRejected =
typeof onRejected === 'function'
? onRejected
: (reason) => {
throw reason
}
// 为了实现链式调用,需要返回一个 MyPromise 对象
const p = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 创建一个微任务
queueMicrotask(() => {
// 获取成功回调的结果
try {
const x = realOnFulfilled(this.value)
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === REJECTED) {
// 如果传入的实例的状态是失败,则执行失败回调
queueMicrotask(() => {
try {
const x = (realo = OnRejected(this.reason))
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
} else if (this.status === PENDING) {
// 如果传入的实例仍是等待中,那么需要先存储成功和失败的回调函数
this.onFulfilledCallback.push(() => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value)
resolvePromise(p, x, resolve, reject)
} catch (error) {
reject(error)
}
})
})
this.onRejectedCallback.push(() => {
queueMicrotask(() => {
try {
const ret = onRejected(this.value)
resolvePromise(p, ret, resolve, reject)
} catch (error) {
reject(error)
}
})
})
}
})
return p
}
// resolve 静态方法
static resolve(parameter) {
if (parameter instanceof MyPromise) {
return parameter
}
return new MyPromise((resolve) => {
resolve(parameter)
})
}
// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
}
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (x instanceof MyPromise) {
// 如果返回值仍然是一个 MyPromise 实例,则进行链式调用
x.then(resolve, reject)
} else {
// x 是普通值,则直接调用成功回调
resolve(x)
}
}
面向 Promise A+ 规范
为了更加符合 Promise A+ 的要求,需要对 resolvePromise
进行优化。
function resolvePromise(promise, x, resolve, reject) {
if (promise === x) {
// 如果返回值和原 promise 相同,抛出循环调用异常
return reject(
new TypeError('Chaining cycle detected for promise #<Promise>')
)
}
if (typeof x === 'object' || typeof x === 'function') {
if (x === null) {
return resolve(x)
}
let then
try {
then = x.then
} catch (error) {
return reject(error)
}
if (typeof then === 'function') {
let called = false
try {
then.call(
x,
(y) => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} catch (error) {
if (called) return
reject(error)
}
} else {
resolve(x)
}
} else {
// x 是普通值,则直接调用成功回调
resolve(x)
}
}
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise