开始学习 Promise 的实现总是让我头疼,很多代码都看不懂,不知道为什么,后来逐渐发现是因为一个重要的原因是对 Promise 用法的不熟悉,或者说只熟悉基本用法。
const p = new Promise((resolve, reject) => {
if (Math.random() > .5) {
resolve('success')
}
reject('fail')
})
比如说 then 方法可以接收两个可选的回调函数作为参数 (onFulfilled
/onRejected
),那就会有四种常见情况:
1. 第一种情况,只传 onFulfilled
:
p.then(
res => {
console.log(res)
}
).catch(
err => {
console.log(err)
}
)
这种用法应该是最常见的,当 Promise 中的 resolve
执行之后,onFulfilled
回调就会被执行并且拿到 resolve
中的结果传参。如果是 reject
被执行,则会触发 .catch
中的 onRejected
回调。
2. 第二种情况,同时传 onFulfilled
和 onRejected
:
p.then(
res => {
console.log(res)
},
err => {
console.log('err from then: ', err)
}
).catch(err => {
console.log('err from catch ', err)
})
此时,当 Promise 中的 reject
执行后 .catch
的回调将不会被触发。
3. 第三种情况,传空:
p
.then()
.then(
res => {
console.log(res)
},
err => {
console.log('err from then', err)
}
).catch(err => {
console.log('err from catch', err)
})
此时将会发生值的透传,空 then
会把值传递给下一个带回调的 then
。
4. 第四种情况,在 onFulfilled
中返回值:
p
.then(
res => {
console.log(res)
return [res, 'plus']
}
)
.then(
res => {
console.log(res) // [res, 'plus']
},
err => {
console.log('err from then', err)
}
).catch(err => {
console.log('err from catch', err)
})
此时返回的值将会被下一个 onFulfilled
接收到。
综上,加上对 Promise 的了解,我们可以这样来形容 Promise:
- Promise 接收一个
executor
函数作为参数,该函数有两个回调函数参数resolve
和reject
分别用于触发和传递成功和失败时的值。 - Promise 的实例需要有一个
then
方法,该方法可以接收两个回调作为参数onFulfilled
/onRejected
,分别用来通过resolve
和reject
方法触发。 - Promise 有三种状态:
pending
,fulfilled
,rejected
,这三种状态只能从pending
转换到其他两种。
基本实现:then 的单次调用
现在让我们来简单定义一个自己的 Promise:
class MyPromise {
static PENDING = 'pending'
static FULFILLED = 'fulfilled'
static REJECTED = 'rejected'
constructor(executor) {
this.state = MyPromise.PENDING
this.value = undefined
this.observerFulfilledCallbacks = []
this.observerRejectedCallbacks = []
const resolve = (val) => {
if (this.state === MyPromise.PENDING) {
this.state = MyPromise.FULFILLED
this.value = val
this.observerFulfilledCallbacks.forEach(cb => cb(val))
}
}
const reject = (reason) => {
if (this.state === MyPromise.PENDING) {
this.state = MyPromise.REJECTED
this.value = reason
this.observerRejectedCallbacks.forEach(cb => cb(reason))
}
}
try {
executor(resolve, reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
onFulfilled && this.observerFulfilledCallbacks.push(onFulfilled)
onRejected && this.observerRejectedCallbacks.push(onRejected)
}
}
这里我们可以发现,Promise 其实应用到了观察者模式,then
方法实现的核心是将回调函数 push (订阅) 到 observerCallbacks 中。这样当执行到 Promise 的状态发生改变,执行到 resolve
或者 reject
的时候,就可以执行 observerCallbacks 中存储的订阅事件。
这样我们已经实现了单次 then
方法调用:
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > .5) {
resolve('success')
}
reject('fail')
}, 1000)
})
p.then(
res => {
console.log(res)
},
err => {
console.log(err)
}
)
进阶实现:then 的链式调用与值透传
现在让我们来实现 Promise 的链式调用。最简单的方式就是在 then
方式实现的最后 return this
:
class MyPromise {
...
then(...) {
...
return this;
}
}
但是这样的实现会有三个的问题。
问题一:无异步事件时回调无法正常执行
const p = new MyPromise((resolve, reject) => {
resolve('success')
})
p.then(
res => {
console.log(res) // ❌ 因为 resolve 之后 state 直接被改变,所以 observerCallbacks 都不会被执行
}
)
问题二: then
方法回调 (onFulfilled
/onRejected
) 中的返回值无法传递
const p = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > .5) {
resolve('success')
}
reject('fail')
}, 1000)
})
p.then(
res => {
console.log(res)
return 'new res'
}
).then(
res => {
console.log(res) // ❌ 我们希望这里的 res 是 'new res' 但依旧是第一次是 'success'
}
)
问题三:值的透传也失败
const p = new Promise((resolve, reject) => {
setTimeout(() => {
if (2 > .5) {
resolve('success')
}
reject('fail')
}, 1000)
})
p.then().then(
res => {
console.log(res) // ❌ 我们希望这里的 res 是 'success'
}
)
这里我们的解决方案是 then
方法最后不返回 this
,而是返回一个新的 Promise 实例:
class MyPromise {
// ...
then(onFulfilled, onRejected) {
// 当用户不传递成功或失败的回调函数时,我们需要给一个默认的回调函数,实现透传
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
return new MyPromise((resolve, reject) => {
const fulfillHandler = (value) => {
try {
const result = onFulfilled(value);
resolve(result);
} catch (err) {
reject(err);
}
};
const rejectHandler = (reason) => {
try {
const result = onRejected(reason);
reject(result);
} catch (err) {
reject(err);
}
};
if (this.state === MyPromise.PENDING) {
this.observerFulfilledCallbacks.push(fulfillHandler)
this.observerRejectedCallbacks.push(rejectHandler)
}
if (this.state === MyPromise.FULFILLED) {
fulfillHandler(this.value)
}
if (this.state === MyPromise.REJECTED) {
rejectHandler(this.value)
}
})
}
}
现在同步事件,回调值的传递,值的透传都可以正常工作了。
但又有新的情况,假如我们在回调中返回 Promise 呢?
const p = new MyPromise((resolve, reject) => {
setTimeout(() => {
if (2 > .5) {
resolve('then1')
}
reject('fail')
}, 1000)
})
p.then(
res => {
console.log(res)
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('then2')
}, 100)
})
}
).then(
res => {
console.log(res)
}
)
我们期待的结果是依次输出 then1,then2。但是我们的代码执行结果是:
then1
MyPromise {
state: 'pending',
value: undefined,
observerFulfilledCallbacks: [],
observerRejectedCallbacks: []
}
显然这不是我们想要的。回看我们的实现,或许可以在 then
的回调事件上做一些调整:
class MyPromise {
//...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
return new MyPromise((resolve, reject) => {
const fulfillHandler = (value) => {
try {
const result = onFulfilled(value);
if (result && typeof result.then === 'function') {
result.then(resolve, reject)
} else {
resolve(result)
}
} catch (err) {
reject(err);
}
};
const rejectHandler = (reason) => {
try {
const result = onRejected(reason);
if (result && typeof result.then === 'function') {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
}
};
if (this.state === MyPromise.PENDING) {
this.observerFulfilledCallbacks.push(fulfillHandler)
this.observerRejectedCallbacks.push(rejectHandler)
}
if (this.state === MyPromise.FULFILLED) {
fulfillHandler(this.value)
}
if (this.state === MyPromise.REJECTED) {
rejectHandler(this.value)
}
})
}
}
到这里,我们的 Promise 其实已经可以覆盖大部分的情况了,我们已经实现了以下功能:
- 通过观察者模式实现
onFulfilled
/onRejected
的异步调用。 then
方法的普通以及异步链式调用。then
方法的值透传。
完备实现:通过 Promises A+ 测试
但是 then
方法还是不够完善,存在以下问题需要解决:
- Promise 的循环引用检查
- 防止多次解析同一个 Promise
- 对
then
方法的错误处理 - 缺少递归解析 Promise
// 1. Promise 的循环引用检查
const p1 = new MyPromise(resolve => resolve());
const p2 = p1.then(() => p2); // Circular reference!
// 2. 多次解析同一个 Promise
const badThenable = {
then(onFulfill, onReject) {
onFulfill(1);
onFulfill(2); // Second call should be ignored
onReject(new Error('Should be ignored')); // Should be ignored
}
};
const p = new MyPromise(resolve => resolve(badThenable));
// 3. 抛错处理
const evilThen = {
get then() {
throw new Error('Accessing .then throws');
}
};
const p = new MyPromise(resolve => resolve(evilThen));
// 4. 递归解析
const p1 = new MyPromise(resolve =>
resolve(new MyPromise(resolve =>
resolve(new MyPromise(resolve =>
resolve('deep value')
))
))
);
为此我们需要将代码调整为以下形式:
const resolvePromise = (thenPromise, ret, resolve, reject) => {
// 解决循环引用的问题
if (thenPromise === ret) {
// Promise 规范要求抛出 TypeError
return reject(new TypeError('Chaining cycle detected for promise'))
}
// 解决多次解析同一个 Promise 的问题 by called flag
let called = false
if (ret !== null && (typeof ret === 'object' || typeof ret === 'function')) {
try {
let then = ret.then;
if (typeof then === 'function') {
then.call(
ret,
res => {
if (called) return
called = true
// 解决递归解析
resolvePromise(thenPromise, res, resolve, reject)
},
err => {
if (called) return
called = true
reject(err)
}
)
} else {
resolve(ret)
}
} catch (err) {
if (called) return
called = true
reject(err)
}
} else {
resolve(ret)
}
}
class MyPromise {
// ...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
const thenPromise = new MyPromise((resolve, reject) => {
const fulfilledHandler = (val) => {
queueMicrotask(() => {
try {
const ret = onFulfilled(val)
resolvePromise(thenPromise, ret, resolve, reject)
} catch (err) {
reject(err)
}
})
}
const rejectedHandler = (reason) => {
queueMicrotask(() => {
try {
const ret = onRejected(reason)
resolvePromise(thenPromise, ret, resolve, reject)
} catch (err) {
reject(err)
}
})
}
if (this.state === MyPromise.PENDING) {
this.observerFulfilledCallbacks.push(fulfilledHandler)
this.observerRejectedCallbacks.push(rejectedHandler)
}
if (this.state === MyPromise.FULFILLED) {
fulfilledHandler(this.value)
}
if (this.state === MyPromise.REJECTED) {
rejectedHandler(this.value)
}
})
return thenPromise
}
}
这样我们的 Promise 实现基本就符合 Promises/A+ 的规范了,可以通过 官方的测试工具 来测试。
补充实现
完成主要方法 then 的实现后,剩下的 2 个实例方法和 5 个静态方法相对会简单很多。
Promise.prototype.catch
class MyPromise {
constructor(executor) {...}
then(onFulfilled, onRejected) {...}
catch(onRejected) {
return this.then(null, onRejected)
}
}
Promise.prototype.finally
class MyPromise {
//...
finally(fn) {
return this.then(fn, fn)
}
}
Promise.resolve
class MyPromise {
// ...
static resolve(val) {
return new MyPromise((resolve, reject) => {
resolve(val)
})
}
}
Promise.reject
class MyPromise {
// ...
static reject(val) {
return new MyPromise((resolve, reject) => {
reject(val)
})
}
}
Promise.all
class MyPromise {
// ...
static all(promises) {
return new MyPromise((resolve, reject) => {
const result = []
let count = 0
for (let i = 0; i < promises.length; i++) {
promises[i].then(
data => {
result[i] = data
if (++count === promises.length) {
resolve(result)
}
},
err => {
reject(err)
}
)
}
})
}
}
Promise.race
class MyPromise {
// ...
static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(p => {
p.then(resolve, reject)
})
})
}
}
Promise.allSettle
class MyPromise {
// ...
static allSettle(promises) {
return new MyPromise((resolve, reject) => {
const results = []
let count = 0
promises.forEach((p, i) => {
p.finally(
value => {
results[i] = {
status: 'fulfilled',
value
};
if (++count === promises.length) {
resolve(results)
}
},
reason => {
results[i] = {
status: 'rejected',
reason
};
if (++count === promises.length) {
reject(results)
}
}
)
});
})
}
}