异步编程是前端开发者必需的技能,过去管理异步的主要机制都是通过函数回调,然而会出现像“回调地狱”这样的问题。为了更好的管理回调,ES6 增加了一个新的特性 Promise。Promise 是 ES7 中 async/await 语法的基础,是 JavaScript 中处理异步的标准形式,现实开发中基本离不开 Promise 了。我们接下来会根据 Promises/A+ 规范自己实现一个 Promise。
完整的代码可以点击我的 github 进行查看,如果你喜欢,欢迎 star,如果发现有问题或者错误,也欢迎提出来。
Promises/A+ 规范
首先我们来看 Promises/A+ 规范的具体内容,Promises/A+ 规范可以查看 Promises/A+,下面是翻译的规范供参考
术语
promise是一个有then方法的对象或者是函数,行为遵循本规范thenable是一个有then方法的对象或者是函数value是promise状态成功时的值,包括undefined、thenable、promiseexception是一个使用throw抛出的异常值reason是promise状态失败时的值
要求
2.1 Promise 状态
Promise 必须处于以下三个状态之一: pending, fulfilled 或者 rejected。
2.1.1 如果 promise 在 pending 状态
2.1.1.1 可以变成 fulfilled 或者是 rejected
2.1.2 如果 promise 在 fulfilled 状态
2.1.2.1 不会变成其它状态
2.1.2.2 必须有一个value值
2.1.3 如果 promise 在 rejected 状态
2.1.3.1 不会变成其它状态
2.1.3.2 必须有一个 promise 被 reject 的 reason
2.2 then 方法
promise 必须提供一个 then 方法,来访问最终的结果
promise 的 then 方法接收两个参数
promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可选参数:
2.2.1.1 onFulfilled 必须是函数类型
2.2.1.2 onRejected 必须是函数类型
2.2.2 如果 onFulfilled 是函数:
2.2.2.1 必须在 promise 变成 fulfilled 时,调用 onFulfilled,参数是 promise 的 value
2.2.2.2 在 promise 的状态不是 fulfilled 之前,不能调用
2.2.2.3 onFulfilled 只能被调用一次
2.2.3 如果 onRejected 是函数:
2.2.3.1 必须在promise变成 rejected 时,调用 onRejected,参数是promise的reason
2.2.3.2 在promise的状态不是 rejected 之前,不能调用
2.2.3.3 onRejected 只能被调用一次
2.2.4 onFulfilled 和 onRejected 应该是微任务
2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
2.2.6 then 方法可能被多次调用
2.2.6.1 如果 promise 变成了 fulfilled 态,所有的 onFulfilled 回调都需要按照 then 的顺序执行
2.2.6.2 如果 promise 变成了 rejected 态,所有的 onRejected 回调都需要按照 then 的顺序执行
2.2.7 then 必须返回一个 promise, promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 执行 resolutionProcedure(promise2, x)
2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常e, promise2 需要被 reject
2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以 promise1 的值 fulfilled
2.2.7.4 如果 onRejected 不是一个函数,promise2 以 promise1 的 reason rejected
2.3 The Promise Resolution Procedure
resolutionProcedure(promise2, x, resolve, reject)
2.3.1 如果 promise2 和 x 相等,那么 promise 执行 reject TypeError
2.3.2 如果 x 是一个 promsie
2.3.2.1 如果 x 是 pending 状态,那么 promise 必须要保持 pending 状态直到 x 变成 fulfilled 或者 rejected 状态
2.3.2.2 如果 x 是 fulfilled 状态, fulfill promise with the same value
2.3.2.3 如果 x 是 rejected 状态, reject promise with the same reason
2.3.3 如果 x 是一个 object 或者 是一个 function
2.3.3.1 let then = x.then.
2.3.3.2 如果 x.then 这步出错,那么 reject promise with e as the reason
2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromise, rejectPromise)
2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行 resolutionProcedure(promise2, y, resolve, reject);
2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r.
2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都调用了,那么第一个调用优先,后面的调用忽略。
2.3.3.3.4 如果调用then抛出异常 e
2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,那么忽略
2.3.3.3.4.2 否则,reject promise with e as the reason
2.3.3.4 如果 then 不是一个function. fulfill promise with x.
2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x.
Promise 源码实现
接下来我们只要按照规范来实现 Promise 就行了,很显然规范后半部分看起来是比较复杂的,尤其是 resolutionProcedure 函数的实现,但其实这只是一些实现的细节而已。初看可能不是那么顺畅,那么强烈建议多看几遍规范,然后自己多实现几遍。
-
promise需要传递一个executor执行器,执行器立刻执行 -
执行器
executor接受两个参数,分别是resolve和reject -
promise只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变 -
promise都有then方法,then接收两个参数,分别是promise成功的回调onFulfilled, 和promise失败的回调onRejected -
如果调用
then时,promise已经成功,则执行onFulfilled,并将promise的值作为参数传递进去。 如果promise已经失败,那么执行onRejected, 并将promise失败的原因作为参数传递进去。如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行。 -
then的参数onFulfilled和onRejected可以缺省 -
promise可以then多次,promise的then方法返回一个新的promise -
如果
then返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调onFulfilled -
如果
then中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调onRejected -
如果
then返回的是一个promise, 那么需要等这个promise,那么会等这个promise执行完。promise如果成功, 就走下一个then的成功,如果失败,就走下一个then的失败
// 定义三种状态的常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// pomise 接收一个 executor 执行器,执行器立刻执行
function MyPromise(executor) {
const _this = this;
// promise 当前的状态
_this.currentState = PENDING;
_this.value = undefined;
// 保存 then 中的回调,只有当 promise 状态为 pending 时才会缓存,并且每个实例至多缓存一个
_this.onFulfilledCallbacks = [];
_this.onRejectedCallbacks = [];
function resolve(value) {
if (value instanceof MyPromise) {
// 如果 value 是个 Promise,调用 then 方法继续执行
value.then(resolve, reject);
}
// 异步执行,保证执行顺序
setTimeout(() => {
if (_this.currentState === PENDING) {
_this.currentState = FULFILLED;
_this.value = value;
_this.onFulfilledCallbacks.forEach(fn => fn());
}
});
}
function reject(reason) {
// 异步执行,保证执行顺序
setTimeout(() => {
if (_this.currentState === PENDING) {
_this.currentState = REJECTED;
_this.value = reason;
_this.onRejectedCallbacks.forEach(fn => fn());
}
});
}
try {
executor(resolve, reject);
} catch(err) {
reject(err);
}
}
MyPromise.prototype.constructor = MyPromise;
MyPromise.prototype.then = function (onFulfilled, onRejected) {
const _this = this;
// 2.2.1 onFulfilled 和 onRejected 都是可选参数
// 2.2.5 onFulfilled 和 onRejected 必须作为函数被调用
// 2.2.7.3 如果 onFulfilled 不是一个函数,promise2 以promise1的值fulfilled
// 2.2.7.4 如果 onRejected 不是一个函数,promise2 以promise1的reason rejected
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : valve => valve;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// 2.2.7,then 必须返回一个新的 promise
const promise2 = new MyPromise((resolve, reject) => {
if (_this.currentState === FULFILLED) {
// 2.2.4 保证 onFulfilled,onRjected 异步执行
setTimeout(() => {
try {
// 2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolutionProcedure
const x = onFulfilled(_this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch(err) {
// 2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 err, promise2 需要被 reject
reject(err);
}
});
}
if (_this.currentState === REJECTED) {
setTimeout(() => {
try {
// 2.2.7.1 onFulfilled 或 onRejected 执行的结果为 x, 调用 resolutionProcedure
const x = onRejected(_this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch(err) {
// 2.2.7.2 如果 onFulfilled 或者 onRejected 执行时抛出异常 err, promise2 需要被 reject
reject(err);
}
});
}
if (_this.currentState === PENDING) {
_this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(_this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
});
});
_this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(_this.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch(err) {
reject(err);
}
});
});
}
});
return promise2;
}
// 2.3 resolutionProcedure(promise2, x, resolve, reject)
function resolutionProcedure(promise2, x, resolve, reject) {
// 2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError
if (promise2 === x) {
reject(new TypeError('Error'));
}
// 2.3.2 如果 x 为 Promise,状态为 pending 需要继续等待否则执行
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(value => {
resolutionProcedure(promise2, value, resolve, reject);
}, reject)
} else {
x.then(resolve, reject);
}
}
// 2.3.3.3.3 reject 或者 resolve 其中一个执行过的话,忽略其他的
let called = false;
// 2.3.3,判断 x 是否为对象或者函数
if ( x && (typeof x === 'object' || typeof x === 'function') ) {
try {
let then = x.then;
// 2.3.3.3 如果 then 是一个函数,then.call(x, resolvePromise, rejectPromise)
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
// 2.3.3.3.1 resolvePromiseFn 的 入参是 y, 执行 resolutionProcedure(promise2, y, resolve, reject);
resolutionProcedure(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
// 2.3.3.3.2 rejectPromise 的 入参是 r, reject promise with r.
reject(r);
}
);
} else {
// 2.3.3.4 如果 then 不是一个 function. fulfill promise with x.
resolve(x);
}
} catch(err) {
// 2.3.3.2 如果 x.then 这步出错,那么 reject promise with err as the reason
if (called) return;
called = true;
reject(err)
}
} else {
// 2.3.4 如果 x 不是一个 object 或者 function,fulfill promise with x.
resolve(x);
}
}
module.exports = MyPromise;
测试
promises-aplus-tests 这个 npm 包可以帮助我们测试所编写的 promise 代码是否符合 Promises/A+ 的规范。
不过我们需要先增加以下代码去提供测试的接口
MyPromise.defer = MyPromise.deferred = function () {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}
通过 npm 安装
npm install promises-aplus-tests --save-dev
通过执行 promises-aplus-tests target.js 可以执行测试,promises-aplus-tests 中共有 872 条测试用例。
可以在我的 github 进行查看,通过 npm run test 来执行测试,可以通过所有的测试用例。
更多精彩内容,欢迎关注微信公众号~