手撕 Promise,做 2 件事就够了:
- 了解 Promise 的行为 ,理解清晰后就能写出
then
方法 - 实现
then
方法,其他方法大多是它的衍生
其他方法,放到最后,很容易能用 then
衍生出来
Promise 行为概览
- 通过
new
调用,说明它是一个class
- 构造时传入一个函数
executor
- 同步执行
executor
- 它有两个参数
resolve
、reject
- 这两个参数负责:改变 Promise 状态、传递数据(分别叫
value
和reason
)
- 同步执行
- 有 3 种状态,
pending
、fulfilled
、rejected
- 初始状态是
pending
- 只能
pending -> fulfilled
或pending -> rejected
- 状态一旦改变后,无法继续调用
fulfilled
、rejected
- 初始状态是
- 有
then
方法- 状态改变后,
then
方法异步 接收resolve/reject
的回调信息 then
内的回调函数实际是 微任务- 可以链式(多次)调用
- 在状态敲定后调用,则立即执行
- 立刻返回一个新
Promise
(pending) 对象(下文称p
):onFulfilled
和onRejected
将被异步执行,即使状态已被敲定p
的行为取决于上一条的执行结果- 返回一个值:以该值作为兑现值
- 无返回:以
undefined
作为兑现值(value) - 抛出错误:以错误作为拒绝值(reason)
- 返回已兑现的
Promise
:以该Promise
作为兑现值 - 返回已拒绝的
Promise
:以该Promise
作为拒绝值 - 返回待定(pending)的
Promise
:保持待定,并在该Promise
状态改变后以其值作为兑现/拒绝值
- 状态改变后,
实现 then
方法
js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Kromise {
constructor(executor) {
this.status = PENDING
// value 和 reason 储存到静态属性里,方便 then 访问
this.value = undefined
this.reason = undefined
// 支持链式调用,以数组保存回调
this.onFulfilledFns = []
this.onRejectedFns = []
// value 传递值
const resolve = (value) => {
// 仅当 pending 时有效
if (this.status === PENDING) {
// 当兑现值是 Promise 时,等待他兑现/拒绝
if (value instanceof Kromise) {
value.then(resolve, reject)
return
}
// Keep simple, 省略 thenable 部分
/**
* 执行回调,微任务:
* 1、确保符合规范
* 2、确保顺序执行 executor 时,不会立即执行 onFulfilled 导致 undefined 错误
*/
queueMicrotask(() => {
// 异步锁,防止异步流程中多次改变状态
if (this.status !== PENDING) return
// 将改变状态代码放到微任务中,为了确保不会过早敲定状态,导致 then 总执行敲定状态后的代码
// 敲定状态
this.status = FULFILLED
// 储存value
this.value = value
this.onFulfilledFns.forEach(fn => fn(this.value))
})
}
}
const reject = (reason) => {
if (this.status === REJECTED) {
queueMicrotask(() => {
if (this.status !== PENDING) return
this.status = REJECTED
this.reason = reason
this.onRejectedFns.forEach(fn => fn(this.reason))
})
}
}
// 同步执行 executor,并传递参数
executor(resolve, reject)
}
then(onFulfilled, onRejected) {
// 返回 Promise,以支持链式调用
return new Kromise((resolve, reject) => {
// 如果在状态敲定后再执行 then,则立即执行回调
if (this.status === FULFILLED && onFulfilled) {
// 将值传递给返回的 p
const value = onFulfilled(this.value)
resolve(value)
}
if (this.status === REJECTED && onRejected) {
const reason = onRejected(this.reason)
reject(reason)
}
// 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
// try...catch 处理抛出错误的情况
this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
})
}
}
function executeFunctionWithErrorCatch(fn, value, resolve, reject) {
try {
// 将自己的值,传递给返回的 Promise p
const result = execFn(value)
resolve(result)
} catch(err) {
reject(err)
}
}
其他方法
catch
实际上是 then(undefined, onRejected)
的简写,但这里有一个边界情况要处理:
如何在then未处理onRejected的情况下,将该错误作为返回值传递给接下来链式调用的catch进行处理?
因为 then
仅注册 onFulfilled
回调时,返回的 p
无法将错误传递下去; 解决方法很简单,只需要提供一个默认的 onRejected
实现,保证错误传递即可
js
catch(onRejected) {
return this.then(undefined, onRejected)
}
// 修改 then 的实现
then(onFulfilled, onRejected) {
// 返回 Promise,以支持链式调用
return new Kromise((resolve, reject) => {
// 如果在状态敲定后再执行 then,则立即执行回调
if (this.status === FULFILLED && onFulfilled) {
// 将值传递给返回的 p
const value = onFulfilled(this.value)
resolve(value)
}
if (this.status === REJECTED && onRejected) {
const reason = onRejected(this.reason)
reject(reason)
}
const defaultOnFulfilled = (value) => value
const defaultOnRejected = (reason) => { throw reason }
onFulfilled = onFulfilled || defaultOnFulfilled
onRejected = onRejected || defaultOnRejected
// 暂存,等状态改变(即 resolve/reject 执行)时才真正调用
// try...catch 处理抛出错误的情况
if (onFulfilled) this.onFulfilledFns.push(() => executeFunctionWithErrorCatch(onFulfilled, this.value, resolve, reject))
if (onRejected) this.onRejectedFns.push(() => executeFunctionWithErrorCatch(onRejected, this.reason, resolve, reject))
})
}
finally
MDN:
Promise
实例的finally()
方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。它会立即返回一个等效的Promise对象
,这可以允许我们链式调用其他 promise 方法
js
finally(onFinally) {
this.then(onFinally, onFinally)
}