前言
在前端开发中,Promise
是一个特别重要的概念,很多的异步操作都依赖于 Promise
。既然在日常中和它打过那么多的交道,那么我们来自己实现一个 Promise
,加深对 Promise
的理解,增强自己的 JavaScript
功力。
本次在实现 Promise
的同时会使用 jest
编写测试用例,以保证实现过程的正确性。
如果想看测试框架的搭建或者完整实现的,可以点击我的github 仓库进行查看,如果你喜欢,欢迎 star,如果你发现我的错误,欢迎提出来。
A+规范
这是一份开放、健全且通用的 Promise
实现规范。由开发者制定,供开发者参考。
这里是官方规范,照着官方规范去实现,就可以写一个属于自己的、符合标准的 Promise
。
实现
话不多说,我们来开始根据 A+ 规范实现 Promise
。
规范的第一节是对一些术语的表达,并无实际功能无需实现。
1. 声明 Promise 类
Promise
是一个类(JavsScript
的类是用函数实现的,只是一个语法糖),它必须接受一个函数,否则报错;它还有一个 then
方法。
class PROMISE {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`);
}
}
then() {}
}
复制代码
测试用例如下:
import Promise from '../';
describe('Promise', () => {
test('是一个类', () => {
expect(Promise).toBeInstanceOf(Function);
expect(Promise.prototype).toBeInstanceOf(Object);
});
test('new Promise() 必须接受一个函数', () => {
expect(() => {
// @ts-ignore
new Promise();
}).toThrow(TypeError);
expect(() => {
// @ts-ignore
new Promise('promise');
}).toThrow(TypeError);
expect(() => {
// @ts-ignore
new Promise(null);
}).toThrow(TypeError);
});
test('new Promise(fn) 会生成一个对象,对象有 then 方法', () => {
const promise = new Promise(() => {});
expect(promise.then).toBeInstanceOf(Function);
});
})
复制代码
测试用例我后面不再列出来了,有兴趣的可以去我的 github 仓库查看。
2.状态维护
Promise
有三种状态:请求态(pending
)、完成态(fulfilled
)和拒绝态(rejected
)。Promise
一开始是请求态,它可以转为另外两种状态(只允许改变一次),它会在状态改变的时候得到一个值。
class PROMISE {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`);
}
// 初始状态
this.status = 'pending';
// 初始值
this.value = null;
// class 内部默认是严格模式,所以需要绑定 this
executor(this.resolve.bind(this), this.reject.bind(this));
}
then() {}
resolve(value) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 改变状态, 赋值
this.status = 'fulfilled';
this.value = value;
}
reject(reason) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 改变状态, 赋值
this.status = 'rejected';
this.value = reason;
}
}
复制代码
到这里,我们就差不多实现了规范 2.1。
3.then
then
可以接受两个函数,会在状态改变之后异步执行,根据规范 2.2.1,如果它们不是函数,就忽略。then
的异步本是一个微任务,这里用宏任务 setTimeout
就将代替一下(如果你想了解微任务、宏任务的知识,欢迎点这里查看我写的关于 Event Loop
的文章)。
then(onFulfilled, onRejected) {
// 暂时将它们变成空函数,后面再做修改
if (typeof onFulfilled !== 'function') {
onFulfilled = () => {};
}
if (typeof onRejected !== 'function') {
onRejected = () => {};
}
if (this.status === 'fulfilled') {
// 异步执行,将就用 setTimeout 实现
setTimeout(() => {
onFulfilled(this.value);
});
}
if (this.status === 'rejected') {
setTimeout(() => {
onRejected(this.value);
});
}
}
复制代码
这里的情况是期待执行 then
函数时,Promise
的状态已经得到了改变。
如果 Promise
的执行函数是一个异步函数,执行 then
的时候,Promise
的状态还没得到改变,那么就需要把 then
接受的两个函数保存起来,等到 resolve
或 reject
的时候执行,这里也要异步执行。
then(onFulfilled, onRejected) {
// 暂时将它们变成同步的空函数,后面再做修改
if (typeof onFulfilled !== 'function') {
onFulfilled = () => {};
}
if (typeof onRejected !== 'function') {
onRejected = () => {};
}
// 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
if (this.status === 'pending') {
this.callbacks.push({
onFulfilled: () => {
setTimeout(() => {
onFulfilled();
});
},
onRejected: () => {
setTimeout(() => {
onRejected();
});
}
});
}
if (this.status === 'fulfilled') {
// 异步执行,将就用 setTimeout 实现
setTimeout(() => {
// 2.2.5
onFulfilled.call(undefined, this.value);
});
}
if (this.status === 'rejected') {
setTimeout(() => {
// 2.2.5
onRejected.call(undefined, this.value);
});
}
}
resolve(value) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 改变状态, 赋值
this.status = 'fulfilled';
this.value = value;
// 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
this.callbacks.forEach((callback) => {
// 2.2.5
callback.onFulfilled.call(undefined, value);
});
}
reject(reason) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 改变状态, 赋值
this.status = 'rejected';
this.value = reason;
// 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
this.callbacks.forEach((callback) => {
// 2.2.5
callback.onRejected.call(undefined, reason);
});
}
复制代码
这样一来,规范的 2.2.2 和 2.2.3 就都实现了。
而且由于 then
里面的 onFulfilled
和 onRejected
都是异步执行的,所以它也满足规范 2.2.4,它会在 Promise
的代码执行之后被调用。
根据规范 2.2.5, onFulfilled
和 onRejected
调用时也不存在 this
,所以用 .call
调用,指定 undefined
为 this
。
规范 2.2.6,如果 then
执行之前 Promise
已经改变了状态,那么直接执行多个 then
。否则将 then
的函数参数存在 callbacks
数组中,后面依次调用,实现规范 2.2.6。
4. 链式操作
Promise
有一个 then
方法,then
之后还可以 then
,那么让 then
返回一个 Promise
即可,根据规范 2.2.7,我们也必须让 then
返回一个 Promise
。
根据规范 2.2.7.1,无论是 onFulfilled
和 onrejected
,它们返回的值都会被当做 then
返回的新的 Promise
的 resolve
的值成功处理。
根据规范 2.2.7.2,如果 onFulfilled
和 onRejected
抛出了一个错误 e
,那么会被当做 then
返回的新的 Promise
的 reject
的值失败处理。
then(onFulfilled, onRejected) {
// then 返回一个 Promise
return new PROMISE((resolve, reject) => {
// 暂时将它们变成空函数,后面再做修改
if (typeof onFulfilled !== 'function') {
onFulfilled = () => {};
}
if (typeof onRejected !== 'function') {
onRejected = () => {};
}
// 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
if (this.status === 'pending') {
this.callbacks.push({
// 这里也需要变了
onFulfilled: () => {
setTimeout(() => {
try {
// 2.2.5
const result = onFulfilled.call(undefined, this.value);
// onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
},
onRejected: () => {
setTimeout(() => {
try {
// 2.2.5
const result = onRejected.call(undefined, this.value);
// onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
});
}
if (this.status === 'fulfilled') {
// 异步执行,将就用 setTimeout 实现
setTimeout(() => {
try {
// 2.2.5
const result = onFulfilled.call(undefined, this.value);
// onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
// 2.2.5
const result = onRejected.call(undefined, this.value);
// onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
});
}
复制代码
规范 2.2.7.3 和 2.2.7.4 表示如果 then
的 onFulfilled
和 onRejected
不是函数,那么新的 Promise
会用上一个 Promise
成功(resolve
)或失败(reject
)的值继续成功或失败,也就是会继承上一个 Promise
的状态和值。
举一个例子
new Promise((resolve, reject) => {
/**
* 执行函数体
*/
})
.then()
.then(
function A() {},
function B() {}
);
复制代码
因为第一个 then
的参数不是函数,那么会发生穿透传递,所以后一个 then
接受的两个参数 function A 和 function B,会根据最前面那个 Promise
的状态和值来进行调用。
也就是上面的代码其实和下面的代码执行结果一样。
new Promise((resolve, reject) => {
/**
* 执行函数体
*/
})
.then(
function A() {},
function B() {}
);
复制代码
好的,让我们来实现这个规范。
其实很简单,你上一个 Promise
如果是 resolve
时,那么我用 then
的 Promise
也 resolve
,且值不变,如果是 reject
,那么 then
的 Promise
也 reject
,且值不变。
这里稍微一点点绕,希望你把这里仔细看,完全搞明白。
if (typeof onFulfilled !== 'function') {
onFulfilled = (value) => {
// 前面的 Promise 是 resolve 时,会调用 onFulfilled
// 那么 then 的新 Promise 也 resolve
// 将状态和值传递给 then 的 then
resolve(value);
};
}
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
// 前面的 Promise 是 reject 时,会调用 onRejected
// 那么 then 的新 Promise 也 reject
// 将状态和值传递给 then 的 then
reject(reason);
};
}
复制代码
其实这里可以简化一下,变成下面这种。
if (typeof onFulfilled !== 'function') {
onFulfilled = resolve;
}
if (typeof onRejected !== 'function') {
onRejected = reject;
}
复制代码
这样,Promise
的链式操作就完成了。
5.实现 2.3.1
接下来我们继续看规范 2.3 的部分。
如果 Promise
和 resolve
或者 reject
调用的值是同一个,那么应该使 Promise
处于拒绝(reject
)态,值为 TypeError
。
代码如下:
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`);
}
// 初始状态
this.status = 'pending';
// 初始值
this.value = null;
// 初始回调数组
this.callbacks = [];
// class 内部默认是严格模式,所以需要绑定 this
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
// 接住 resolve 和 reject 抛出的 TypeError,作为 reject 的值调用
this.reject(error);
}
}
/**
* 代码
*/
resolve(value) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 如果 promise 和 resolve 调用的值是同一个,那么就抛出错误
if (value === this) {
throw new TypeError();
}
// 改变状态, 赋值
this.status = 'fulfilled';
this.value = value;
// 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
this.callbacks.forEach((callback) => {
callback.onFulfilled.call(undefined, value);
});
}
reject(reason) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 如果 promise 和 reject 调用的值是同一个,那么就抛出错误
if (value === this) {
throw new TypeError();
}
// 改变状态, 赋值
this.status = 'rejected';
this.value = reason;
this.callbacks.forEach((callback) => {
callback.onRejected.call(undefined, reason);
});
}
复制代码
6.实现 2.3.3
规范 2.3.2 是 2.3.3 情况的一个子集,我们直接实现 2.3.3 就可以了。
规范 2.3.3 说了那么多,其实就是在 resolve
和 reject
中添加下面几行代码。
if (value instanceof Object) {
// 2.3.3.1 2.3.3.2
const then = value.then;
// 2.3.3.3
if (typeof then === 'function') {
return then.call(
value,
this.resolve.bind(this),
this.reject.bind(this)
);
}
}
复制代码
关于规范 2.3.3.2,我理解的是,并不是说 x.then 是一个异常,而是在取值的过程中发生了一个异常,代码表达如下:
// 不是这种
const X = {
then: new Error()
}
// 是类似这种的情况
const x = {};
Object.defineProperty(x, 'then', {
get: function() {
throw new Error('y');
}
});
new Promise((resolve, reject) => {
resolve(x)
}).then((value) => {
console.log('fulfilled', value)
}, (reason) => {
console.log('rjected', reason)
})
复制代码
由于取值 x.then 的过程中抛出了一个异常,被 constructor
中的 try catch
捕捉到了,执行 reject
,这里就无需做处理了。
规范 2.3.4 不用特殊实现,说的就是正常情况。
7.完整实现
到这里就把 A+ 规范走了一遍,实现的 Promise
如下:
class PROMISE {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError(`Promise resolver ${executor} is not a function`);
}
// 初始状态
this.status = 'pending';
// 初始值
this.value = null;
// 初始回调数组
this.callbacks = [];
// class 内部默认是严格模式,所以需要绑定 this
try {
executor(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
// 接住 resolve 和 reject 抛出的 TypeError,作为 reject 的值调用
this.reject(error);
}
}
then(onFulfilled, onRejected) {
// then 返回一个 Promise
return new PROMISE((resolve, reject) => {
// then 的穿透传递
if (typeof onFulfilled !== 'function') {
onFulfilled = resolve;
}
if (typeof onRejected !== 'function') {
onRejected = reject;
}
// 如果执行 then 的时候,Promise 状态还未发生变化,就先将这两个函数存起来
if (this.status === 'pending') {
this.callbacks.push({
onFulfilled: () => {
setTimeout(() => {
try {
const result = onFulfilled.call(undefined, this.value);
// onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
},
onRejected: () => {
setTimeout(() => {
try {
const result = onRejected.call(undefined, this.value);
// onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
});
}
if (this.status === 'fulfilled') {
// 异步执行,将就用 setTimeout 实现
setTimeout(() => {
try {
const result = onFulfilled.call(undefined, this.value);
// onFulfilled 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
if (this.status === 'rejected') {
setTimeout(() => {
try {
const result = onRejected.call(undefined, this.value);
// onRejected 的返回值当做新的 Promise 的 resolve 的值被调用
resolve(result);
} catch (error) {
// 如果抛出了错误,那么当做新的 Promise 的 reject 的值被调用
reject(error);
}
});
}
});
}
resolve(value) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 如果 promise 和 resolve 调用的值是同一个,那么就抛出错误
if (value === this) {
throw new TypeError('Chaining cycle detected for promise');
}
if (value instanceof Object) {
// 2.3.3.1
const then = value.then;
// 2.3.3.3
if (typeof then === 'function') {
return then.call(
value,
this.resolve.bind(this),
this.reject.bind(this)
);
}
}
// 改变状态, 赋值
this.status = 'fulfilled';
this.value = value;
// 如果回调函数数组中有值,说明之前执行过 then,需要调用 then 接受的函数
this.callbacks.forEach((callback) => {
callback.onFulfilled.call(undefined, value);
});
}
reject(reason) {
// 状态保护
if (this.status !== 'pending') {
return;
}
// 如果 promise 和 reject 调用的值是同一个,那么就抛出错误
if (reason === this) {
throw new TypeError('Chaining cycle detected for promise');
}
if (reason instanceof Object) {
// 2.3.3.1
const then = reason.then;
// 2.3.3.3
if (typeof then === 'function') {
return then.call(
reason,
this.resolve.bind(this),
this.reject.bind(this)
);
}
}
// 改变状态, 赋值
this.status = 'rejected';
this.value = reason;
this.callbacks.forEach((callback) => {
callback.onRejected.call(undefined, reason);
});
}
}
复制代码
其中重复的代码我在这里就不抽离出来了,这样方便阅读。
8.静态方法
像 resolve
、reject
、all
、race
这几个静态方法其实不属于 A+ 规范中,我这里也顺带实现一下。
resolve
和 reject
类似,接受一个值,返回一个 Promise
。如果接受的值是一个 Promise
,那么就继承该 Promise
的状态和值。
static resolve(value) {
return new PROMISE((resolve, reject) => {
if (value instanceof PROMISE) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
static reject(reason) {
return new PROMISE((resolve, reject) => {
if (reason instanceof PROMISE) {
reason.then(resolve, reject);
} else {
reject(reason);
}
});
}
复制代码
all
是接受一个 Promise
数组,返回一个 Promise
。
这里定义一个 results
数组,然后遍历 Promise
数组,每 resolve
一个 Promise
,就像 results
加入一个 resolve
的值,如果results
的长度与 Promise
数组的长度相同,那么说明全部的 Promise
都 resolve
了,那么 all
返回的 Promise
就 resolve
这个数组。
另外,只要 Promise
数组中有一个 Promise
转为了 reject
,那么 all
返回的 Promise
也 reject
掉。
static all(promiseArray) {
return new PROMISE((resolve, reject) => {
const results = [];
promiseArray.forEach((promise) => {
promise.then((value) => {
results.push(value);
if (results.length === promiseArray.length) {
resolve(results);
}
}, reject);
});
});
}
复制代码
race
也是接受一个 Promise
数组,返回一个 Promise
。
只有 Promise
数组中有一个 Promise
resolve
或者 reject
了,那么 race
返回的 Promise
也 resolve
或者 reject
。
static race(promiseArray) {
return new PROMISE((resolve, reject) => {
promiseArray.forEach((promise) => {
promise.then(resolve, reject);
});
});
}
复制代码
感想
从头实现一遍 Promise
的 A+ 规范的过程中,对 Promise
的一些细枝细节都梳理了一遍,一些之前根本没有注意到的地方也给暴露出来了,特别是 x 如果是一个有 then
方法的对象,那么 x 会被包装成一个 Promise
,这个地方也是之前没有接触到的。
这个过程我用 TypeScript
实现了一遍,具体代码点这里,其中也包括了我写的测试用例,如果你喜欢,欢迎 star。
如果你发现我实现过程有不对的地方,欢迎与我探讨。