手写一个Promises/A+和理解核心概念
这篇文章我们通过划分功能逐个实现 Promise 的功能,了解这个异步任务处理机制的实现,知道 Promise 实例 then 方法的链式调用原理,梳理 Promise 核心的裁决过程,并顺利通过Promise/A+规范的测试用例库。
一、安装和运行测试用例库
- 初始化项目安装 promises-aplus-tests 测试用例库
mkdir myPromise && cd myPromise
npm init -y && npm i promises-aplus-tests -D
- 创建一个 index.js 文件并输入用例库使用规则
module.exports = {
resolved: () => {},
rejected: () => {},
deferred: () => {},
};
- 本地跑一下测试用例库
npx promises-aplus-tests index.js
- 查看测试结果(871个测试用例全部失败了)
可以看到我们测试失败的原因都是没有定义某些属性或者方法,接下来我们就要实现功能并通过这八百多个测试用例了。
二、基本状态处理和回调执行
这里主要定义 Promise 的三种状态和执行器函数(executor),并处理执行器函数中的回调函数。
class MyPromise {
static PENDING = 'pending'; // 进行中
static FULFILLED = 'fulfilled'; // 已成功
static REJECTED = 'rejected'; // 已失败
state = MyPromise.PENDING;
value = null;
reason = null;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
//实例化时会立即执行
executor(this.resolve, this.reject);
} catch (reason) {
this.reject(reason);
}
}
resolve = (value) => {
if (this.state === MyPromise.PENDING) {
//切换为成功状态
this.state = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => callback());
}
};
reject = (reason) => {
if (this.state === MyPromise.PENDING) {
//切换为失败状态
this.state = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => callback());
}
};
then(onFulfilled, onRejected) {
// 根据状态判断处理两个回调函数
switch (this.state) {
case MyPromise.FULFILLED:
// 处理用户想要的成功回调
onFulfilled(this.value);
break;
case MyPromise.REJECTED:
// 处理用户想要的失败回调
onFulfilled(this.value);
break;
case MyPromise.PENDING:
// 进行中的Promise先存储回调
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
break;
}
}
}
三、完善 then 方法的异步和链式调用
我们知道 Promise 实例的 then 方法的回调函数是晚于同步任务的,因此可以用 setTimeout 来模拟异步,同时因为 then 支持链接调用,所以方法要返回的是一个新的 Promise 实例。
then(onFulfilled, onRejected) {
// 添加:如果 onFulfilled 不是函数,其必须被忽略 见规范2.2.1.2
if (typeof onFulfilled != 'function') {
onFulfilled = (value) => value;
}
// 添加:如果 onRejected 不是函数,其必须被忽略 见规范2.2.1.2
if (typeof onRejected != 'function') {
onRejected = (reason) => {
throw reason;
};
}
return new MyPromise((resolve, reject) => {
switch (this.state) {
case MyPromise.FULFILLED:
// 添加:setTimeout模拟异步
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.REJECTED:
// 添加:setTimeout模拟异步
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.PENDING:
this.onFulfilledCallbacks.push(() => {
// 添加:setTimeout模拟异步
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (reason) {
reject(reason);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// 添加:setTimeout模拟异步
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (reason) {
reject(reason);
}
}, 0);
});
break;
}
});
}
四、核心部分 resolve Promise
上面既然 then 方法返回一个新的 Promise 实例,但 then 方法本身就支持传入一个 Promise 作为回调函数,那么会不会遇到循环调用的问题呢?答案是会的。因此规范提出了一些 Promise 裁决过程,这也是 Promise 最重要的部分,有区分了很多种情况。
then = (onFulfilled, onRejected) => {
if (typeof onFulfilled != 'function') {
onFulfilled = (value) => value;
}
if (typeof onRejected != 'function') {
onRejected = (reason) => {
throw reason;
};
}
// Promise 核心解决过程 见规范2.3
const _resolvePromise = (promise, x, resolve, reject) => {
// 2.3.1 如果 promise 和 x 指向同一对象,避免循环引用,抛出 TypeError 错误
if (promise === x) {
const errMsg = 'The promise and the return value are the same';
return reject(new TypeError(errMsg));
}
// 2.3.3 如果 x 为对象(不是null)或者函数
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let then = null;
try {
// 2.3.3.1. 检索属性 x.then
then = x.then;
} catch (error) {
// 2.3.3.2 如果 x.then 导致抛出异常 e,则以 e 为拒绝原因拒绝 promis
return reject(error);
}
// 2.3.3.3 如果 then 是一个函数,x 作为 then 的 this 调用该方法,第一个参数是成功的回调函数,第二个参数是失败的回调函数
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
(y) => {
// 2.3.3.3.4 如果成功回调与失败回调都被调用或多次调用同一个参数,则第一个调用优先,其他调用都将被忽略。
if (called) return;
called = true;
// 2.3.3.3.1 如果成功回调以值 y 调用,运行 [[Resolve]](promise,y)
_resolvePromise(promise, y, resolve, reject);
},
(r) => {
// 2.3.3.3.4 如果成功回调与失败回调都被调用或多次调用同一个参数,则第一个调用优先,其他调用都将被忽略。
if (called) return;
called = true;
// 2.3.3.3.2 如果失败回调以原因 r 调用,用 r 拒绝 promise
reject(r);
}
);
} catch (error) {
// 2.3.3.4 如果调用 then 方法抛出异常 e:
// 2.3.3.4.1 若成功回调或失败回调都调用过,忽略
if (called) return;
// 2.3.3.4.2 未调用,用 e 作为原因拒绝 promise
reject(error);
}
} else {
// 2.3.3.4. 如果 then 不是函数,用 x 作为值完成 promise
return resolve(x);
}
} else {
// 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
return resolve(x);
}
};
// 链式返回的Promise
const newPromise = new MyPromise((resolve, reject) => {
switch (this.state) {
case MyPromise.FULFILLED:
setTimeout(() => {
try {
const x = onFulfilled(this.value);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.REJECTED:
setTimeout(() => {
try {
const x = onRejected(this.reason);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.PENDING:
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
break;
}
});
return newPromise;
};
五、The Promise Resolution Procedure
Promises/A+ 规范中 2.3 The Promise Resolution Procedure 比较复杂,是 Promise 裁决过程,因此这里也提供了一份原文参照。
六、完整的 Promises/A+ 代码实现
下面是完整的 Promise 手写代码和测试用例库使用。
let promisesAplusTests = require('promises-aplus-tests');
class MyPromise {
static PENDING = 'pending'; // 进行中
static FULFILLED = 'fulfilled'; // 已成功
static REJECTED = 'rejected'; // 已失败
state = MyPromise.PENDING;
value = null;
reason = null;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
constructor(executor) {
try {
//实例化时会立即执行
executor(this.resolve, this.reject);
} catch (reason) {
this.reject(reason);
}
}
resolve = (value) => {
if (this.state === MyPromise.PENDING) {
//切换为成功状态
this.state = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => callback());
}
};
reject = (reason) => {
if (this.state === MyPromise.PENDING) {
//切换为失败状态
this.state = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => callback());
}
};
then = (onFulfilled, onRejected) => {
if (typeof onFulfilled != 'function') {
onFulfilled = (value) => value;
}
if (typeof onRejected != 'function') {
onRejected = (reason) => {
throw reason;
};
}
// Promise 核心解决过程 见规范2.3
const _resolvePromise = (promise, x, resolve, reject) => {
// 2.3.1 如果 promise 和 x 指向同一对象,抛出 TypeError 错误
if (promise === x) {
const errMsg = 'The promise and the return value are the same';
return reject(new TypeError(errMsg));
}
// 2.3.3 如果 x 为对象(不是null)或者函数
if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
let then = null;
try {
// 2.3.3.1. 检索属性 x.then
then = x.then;
} catch (error) {
// 2.3.3.2 如果 x.then 导致抛出异常 e,则以 e 为拒绝原因拒绝 promis
return reject(error);
}
// 2.3.3.3 如果 then 是一个函数,x 作为 then 的 this 调用该方法,第一个参数是成功的回调函数,第二个参数是失败的回调函数
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
(y) => {
// 2.3.3.3.4 如果成功回调与失败回调都被调用或多次调用同一个参数,则第一个调用优先,其他调用都将被忽略。
if (called) return;
called = true;
// 2.3.3.3.1 如果成功回调以值 y 调用,运行 [[Resolve]](promise,y)
_resolvePromise(promise, y, resolve, reject);
},
(r) => {
// 2.3.3.3.4 如果成功回调与失败回调都被调用或多次调用同一个参数,则第一个调用优先,其他调用都将被忽略。
if (called) return;
called = true;
// 2.3.3.3.2 如果失败回调以原因 r 调用,用 r 拒绝 promise
reject(r);
}
);
} catch (error) {
// 2.3.3.4 如果调用 then 方法抛出异常 e:
// 2.3.3.4.1 若成功回调或失败回调都调用过,忽略
if (called) return;
// 2.3.3.4.2 未调用,用 e 作为原因拒绝 promise
reject(error);
}
} else {
// 2.3.3.4. 如果 then 不是函数,用 x 作为值完成 promise
return resolve(x);
}
} else {
// 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
return resolve(x);
}
};
// 链式返回的Promise
const newPromise = new MyPromise((resolve, reject) => {
switch (this.state) {
case MyPromise.FULFILLED:
setTimeout(() => {
try {
const x = onFulfilled(this.value);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.REJECTED:
setTimeout(() => {
try {
const x = onRejected(this.reason);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
break;
case MyPromise.PENDING:
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
_resolvePromise(newPromise, x, resolve, reject);
} catch (reason) {
reject(reason);
}
}, 0);
});
break;
}
});
return newPromise;
};
}
/* 测试用例使用规则 */
MyPromise.deferred = function () {
let result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
/* 测试用例自定义输入 */
promisesAplusTests(MyPromise, function (err) {
let { message = 'success' } = err || {};
console.log('[promises-aplus-tests]', message);
});
module.exports = MyPromise;
七、总结
手写 Promise 并不难,分成几个步骤就比较容易理解了。我们在业务一般都是使用 Promise 实例的 then 方法,那么就能知道 then 方法的传入参数和返回内容。同时因为得到的内容与前面的 Promise 实例化有关,那么就知道该 Promise 在实例化时会保存一些状态和一些回调函数,在适当的时候进行处理。
我们也知道 then 方法是支持多次调用的,他们的状态一旦完成就不会变化,所以使用一个处理成功态的函数数组和处理失败态的函数数组,在链式调用时也是因为 then 回返回一个新的 Promise 实例。
至于最复杂的 resolve Promise 模块,是属于底层逻辑处理了,一般我们不会感知,除了循环调用的情况,例如下面这几行代码。但一般我们不遇到各种负责多变的 Promise 面试题的话,是不用细究实现原理的。
const p = new Promise((resolve, reject) => {
resolve(1);
});
const newPromise = p.then((data) => {
return newPromise;
});
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>