前言
在前端开发中,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。
如果你发现我实现过程有不对的地方,欢迎与我探讨。