0. 前言
面试官:「你写个 Promise 吧。」
我:「对不起,打扰了,再见!」
现在前端越来越卷,不会手写 Promise
都不好意思面试了(手动狗头.jpg)。虽然没多少人会在业务中用自己实现的 Promise
,但是,实现 Promise
的过程会让你对 Promise
更加了解,出了问题也可以更好地排查。
如果你还不熟悉 Promise,建议先看一下 MDN 文档。
在实现 Promise 之前,我建议你先看一遍 Promises/A+ 规范(中文翻译:Promise A+ 规范),本文中不会再次介绍相关的概念。推荐大家优先阅读原版英文,只需高中水平的英语知识就够了,遇到不懂的再看译文。
另外,本文将使用 ES6 中的 Class 来实现 Promise
。为了方便大家跟 Promise/A+ 规范对照着看,下文的顺序将按照规范的顺序来行文。
在正式开始之前,我们新建一个项目,名称随意,按照以下步骤进行初始:
- 打开 CMD 或 VS Code,运行
npm init
,初始化项目 - 新建
PromiseImpl.js
文件,后面所有的代码实现都写在这个文件里
完整代码地址:ashengtan/promise-aplus-implementing
1. 术语
这部分大家直接看规范就好,也没什么好解释的。注意其中对于 value
的描述,value
可以是一个 thenable
(有 then
方法的对象或函数) 或者 Promise
,这点会在后面的实现中体现出来。
2. 要求
2.1 Promise 的状态
一个 Promise
有三种状态:
pending
:初始状态fulfilled
:成功执行rejected
:拒绝执行
一个 Promise
一旦从 pending
变为 fulfilled
或 rejected
,就无法变成其他状态。当 fulfilled
时,需要给出一个不可变的值;同样,当 rejected
时,需要给出一个不可变的原因。
根据以上信息,我们定义 3 个常量,用来表示 Promise
的状态:
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
接着,我们先把 Promise
的基础框架先定义出来,这里我使用 ES6 的 Class 来定义:
class PromiseImpl {
constructor() {
}
then(onFulfilled, onRejected) {
}
}
这里我们先回想一下 Promise
的基本用法:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
// 多次调用
const p1 = promise.then()
const p2 = promise.then()
const p3 = promise.then()
好了,继续完善 PromiseImpl
,先完善一下构造方法:
class PromiseImpl {
constructor() {
// `Promise` 当前的状态,初始化时为 `pending`
this.status = STATUS_PENDING
// fulfilled 时的值
this.value = null
// rejected 时的原因
this.reason = null
}
}
另外,我们还要定义两个方法,用于 fulfilled
和 rejected
时回调:
class PromiseImpl {
constructor() {
// ...其他代码
// 2.1.2 When `fulfilled`, a `promise`:
// 2.1.2.1 must not transition to any other state.
// 2.1.2.2 must have a value, which must not change.
const _resolve = value => {
// 如果 `value` 是 `Promise`(即嵌套 `Promise`),
// 则需要等待该 `Promise` 执行完成
if (value instanceof PromiseImpl) {
return value.then(
value => _resolve(value),
reason => _reject(reason)
)
}
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
}
}
// 2.1.3 When `rejected`, a `promise`:
// 2.1.3.1 must not transition to any other state.
// 2.1.3.2 must have a reason, which must not change.
const _reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
}
}
}
}
注意,在 _resolve()
中,如果 value
是 Promise
的话(即嵌套 Promise
),则需要等待该 Promise
执行完成。这点很重要,因为后面的其他 API 如 Promise.resolve
、Promise.all
、Promise.allSettled
等均需要等待嵌套 Promise
执行完成才会返回结果。
最后,别忘了在 new Promise()
时,我们需要将 resolve
和 reject
传给调用者:
class PromiseImpl {
constructor(executor) {
// ...其他代码
try {
executor(_resolve, _reject)
} catch (e) {
_reject(e)
}
}
}
使用 trycatch
将 executor
包裹起来,因为这部分是调用者的代码,我们无法保证调用者的代码不会出错。
2.2 Then 方法
一个 Promise
必须提供一个 then
方法,其接受两个参数:
promise.then(onFulfilled, onRejected)
class PromiseImpl {
then(onFulfilled, onRejected) {
}
}
2.2.1 onFulfilled
和 onRejected
从规范 2.2.1 中我们可以得知以下信息:
onFulfilled
和onRejected
是可选参数- 如果
onFulfilled
和onRejected
不是函数,则必须被忽略
因此,我们可以这样实现:
class PromiseImpl {
then(onFulfilled, onRejected) {
// 2.2.1 Both `onFulfilled` and `onRejected` are optional arguments:
// 2.2.1.1 If `onFulfilled` is not a function, it must be ignored
// 2.2.1.2 If `onRejected` is not a function, it must be ignored
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {
}
onRejected = typeof onRejected === 'function' ? onRejected : () => {
}
}
}
2.2.2 onFulfilled
特性
从规范 2.2.2 中我们可以得知以下信息:
- 如果
onFulfilled
是一个函数,则必须在fulfilled
后调用,第一个参数为promise
的值 - 只能调用一次
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// 2.2.2 If `onFulfilled` is a function:
// 2.2.2.1 it must be called after `promise` is fulfilled,
// with promise’s value as its first argument.
// 2.2.2.2 it must not be called before `promise` is fulfilled.
// 2.2.2.3 it must not be called more than once.
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
}
}
2.2.3 onRejected
特性
与 onFulfilled
同理,只不过是在 rejected
时调用,第一个参数为 promise
失败的原因
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// 2.2.3 If onRejected is a function:
// 2.2.3.1 it must be called after promise is rejected,
// with promise’s reason as its first argument.
// 2.2.3.2 it must not be called before promise is rejected.
// 2.2.3.3 it must not be called more than once.
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
}
}
2.2.4 异步执行
在日常开发中,我们经常使用 Promise
来做一些异步操作,规范 2.2.4 就是规定异步执行的问题,具体的可以结合规范里的注释阅读,重点是确保 onFulfilled
和 onRejected
要异步执行。
需要指出的是,规范里并没有规定 Promise
一定要用 micro-task
机制来实现,因此你使用 macro-task
机制来实现也是可以的。当然,现在浏览器用的是 micro-task
来实现。这里为了方便,我们使用 setTimeout
(属于 macro-task
)来实现。
因此,我们需要稍微改造下上面的代码:
class PromiseImpl {
then(onFulfilled, onRejected) {
// ...其他代码
// fulfilled
if (this.status === STATUS_FULFILLED) {
setTimeout(() => {
onFulfilled(this.value)
}, 0)
}
// rejected
if (this.status === STATUS_REJECTED) {
setTimeout(() => {
onRejected(this.reason)
}, 0)
}
}
}
2.2.5 onFulfilled
和 onRejected
必须作为函数被调用
这个已经在上面实现过了。
2.2.6 then
可被多次调用
举个例子:
const promise = new Promise((resolve, reject) => {
// ...do something
resolve(value) // or reject(error)
})
promise.then()
promise.then()
promise.catch()
因此,必须确保当 Promise
fulfilled
或 rejected
时,onFulfilled
或 onRejected
按照其注册的顺序逐一回调。还记得最开始我们定义的 resolve
和 reject
吗?这里我们需要改造下,保证所有的回调都被执行到:
const invokeArrayFns = (fns, arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](arg)
}
}
class PromiseImpl {
constructor(executor) {
// ...其他代码
// 用于存放 `fulfilled` 时的回调,一个 `Promise` 对象可以注册多个 `fulfilled` 回调函数
this.onFulfilledCbs = []
// 用于存放 `rejected` 时的回调,一个 `Promise` 对象可以注册多个 `rejected` 回调函数
this.onRejectedCbs = []
const resolve = value => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
// 2.2.6.1 If/when `promise` is fulfilled,
// all respective `onFulfilled` callbacks must execute
// in the order of their originating calls to `then`.