一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
上一节我们实现了一个简单版的Promise,通过手写让我们理解了Promise的原理,可以点击查看。但是,它比较简单薄弱。这一节我们来实现一个完整版的Promise,它遵循Promise A+的标准,且能够通过测试。
我们先搭建一个Promise的骨架:
function MyPromise(executor) {}
MyPromise.prototype.then = function() {}
MyPromise.prototype.catch = function() {}
在开始编写程序之前,我们先了解一下Promise的标准(如果觉得标准过长,可以先跳过看后面的代码实现,在回过来看这里的标准):
-
Promise的三种状态:pending、fulfilled、rejected
-
处于pending中可以转fulfilled或rejected
-
处于fulfilled中不能迁移其他任何状态,必须拥有不可变的终值
-
处于rejected中不能迁移其他任何状态,必须拥有不可变的拒绝原因
-
-
Then方法
-
Promise拥有一个
then方法,且接受两个参数onFulfilled,onRejected -
then方法的参数是可选的,如果不是参数可以被忽略
-
onFulfilled如果是函数,当
promise执行结束后被调用,其第一个参数是promise的终值,该方法被调用不超过一次 -
onRejected如果是函数,当
promise被拒绝后,其第一个参数是promise的错误原因,该方法被调用不超过一次 -
then方法可以被多次调用(链式调用)
-
当
promise成功执行时,所有onFulfilled需按照其注册顺序依次回调 -
当
promise被拒绝执行时,所有的onRejected需按照其注册顺序依次回调
-
-
then必须返回一个promise对象
-
-
Promise解决过程
-
如果promise和x指向同一对象,以TypeError为因拒绝执行
-
如果
x为 Promise ,则使promise接受x的状态-
如果
x处于等待态,promise需保持为等待态直至x被执行或拒绝 -
如果
x处于执行态,用相同的值执行promise -
如果
x处于拒绝态,用相同的据因拒绝promise
-
-
如果
x为对象或者函数:-
把
x.then赋值给then -
如果取
x.then的值时抛出错误e,则以e为据因拒绝promise -
如果
then是函数,将x作为函数的作用域this调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise,第二个参数叫做rejectPromise:-
如果
resolvePromise以值y为参数被调用,则运行[[Resolve]](promise, y) -
如果
rejectPromise以据因r为参数被调用,则以据因r拒绝promise -
如果
resolvePromise和rejectPromise均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 -
如果调用
then方法抛出了异常e:-
如果
resolvePromise或rejectPromise已经被调用,则忽略之 -
否则以
e为据因拒绝promise
-
-
如果
then不是函数,以x为参数执行promise
-
-
如果
x不为对象或者函数,以x为参数执行promise
-
-
以上的规范冗长又晦涩,现在我们根据标准一步步拆解来实现通用的Promise:
Promise有三种状态:pending、fulfilled、rejected,同时传入executor作为参数。
// promise的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
function MyPromise(executor) {
this.value = null
this.reason = null
this.state = PENDING
const that = this
}
我们先定义了三种状态:pending、fulfilled、rejected。同时,声明了MyPromise构造函数,定义value来存放异步任务成功的结果,定义reason存放异步任务失败的原因,初始化state为pending状态。 接着加上resolve和rejecte两个函数,这两个函数将作为executor的入参:
function MyPromise(executor) {
// ...
// 新增代码
function resolve(value) {
if (that.state === PENDING) {
that.value = value;
that.state = FULFILLED;
}
}
function reject(reason) {
if (that.state === PENDING) {
that.reason = reason;
that.state = REJECTED;
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
上面的代码,我们新增了resolve和reject函数作为executor的入参。resolve和reject函数中,有判断当前state的状态,只有是pending的状态才能迁移到fulfilled或者rejected的状态,保证了状态的不可逆。那这里resolve和reject两个函数的作用又是什么呢?其实是用来接受异步执行的函数结果:resolve用来接受异步执行成功的值,reject用来接受异步执行失败的原因。最后executor用trycatch来包裹,当执行报错的时候,直接调用reject返回报错原因。
现在我们实现一下then方法:
MyPromise.prototype.then = function(onFulfilled, onRejected) {
// 新增以下代码
if (typeof onFulfilled !== 'function') {
onFulfilled = value => value
}
if (typeof onRejected !== 'function') {
onRejected = reason => { throw reason }
}
if (this.state === FULFILLED) {
onFulfilled(this.value)
} else if (this.state === REJECTED) {
onRejected(this.reason)
}
}
在then方法中,如果不传onFulfilled或 onRejected,我们给设定一个默认的函数的方式来实现更加通用的onFulfilled和onRejected。接着判断state为fulfilled,调用onFulfilled并把结果传递出去。如果是拒绝状态,则调用onRejected返回报错原因。
接着我们实现then方法的异步调用:
上面的代码中,调用then之后onFulfilled和onRejected是立即执行的,如果有pending状态的任务,那么它也会立即执行,出现不符合预期的行为。所以我们需要给then添加异步任务,将异步任务添加到队列中,当状态为终态(fulfilled或rejected)时,才按顺序执行异步任务。代码如下:
function MyPromise(executor) {
// ...
// 新增代码
this.fulfilledQueue = [];
this.rejectedQueue = [];
function resolve(value) {
if (that.state === PENDING) {
that.value = value;
that.state = FULFILLED;
// 新增代码
that.fulfilledQueue.forEach((fn) => fn());
}
}
function reject(reason) {
if (that.state === PENDING) {
that.reason = reason;
that.state = REJECTED;
// 新增代码
that.rejectedQueue.forEach((fn) => fn());
}
}
// ...
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// ...
// 新增代码
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
onFulfilled(this.value);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
onRejected(this.reason);
} catch (error) {
reject(error);
}
});
};
if (this.state === FULFILLED) {
fulfilledMicrotask();
} else if (this.state === REJECTED) {
rejectedMicrotask();
} else {
// 新增代码:pending状态时注册异步任务队列
this.fulfilledQueue.push(fulfilledMicrotask);
this.rejectedQueue.push(rejectedMicrotask);
}
}
我们通过维护两个队列用来存放异步函数的,这两个队列的函数只有在状态迁移到终态的时候才能执行,保证了异步函数在状态改变后才被触发。同时,我们使用queueMicrotask来包装异步函数fulfilledMicrotask 和rejectedMicrotask 。
现在已经实现了异步任务了,接下来我们实现链式调用。所谓链式调用,就是then后面可以继续调用then方法,同时后面的then方法可以拿到前面一个then方法返回的值。代码如下:
MyPromise.prototype.then = function (onFulfilled, onRejected) {
// ...
// 新增以下代码
const p2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
// 新增以下代码
try {
const x = onFulfilled(this.value);
resolutionPromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
// 新增以下代码
try {
const x = onRejected(this.reason);
resolutionPromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === FULFILLED) {
fulfilledMicrotask();
} else if (this.state === REJECTED) {
rejectedMicrotask();
} else {
this.fulfilledQueue.push(fulfilledMicrotask);
this.rejectedQueue.push(rejectedMicrotask);
}
});
return p2;
};
我们可以发现,这里我们通过再次new MyPromise返回自身,实现了then方法可以继续调用then 方法。但是,后面的then方法怎么取到前面一个then 方法的值呢?这里就进入到Promise的解决程序,即resolutionPromise方法。那么我们来看一下resolutionPromise方法(这是最后一个了!)
function resolutionPromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError("循环引用报错"));
}
if (x !== null && (typeof x === "object" || typeof x === "function")) {
let isCall = false; // 标识then是否调用,避免重复调用
try {
let then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (isCall) return;
isCall = true;
resolutionPromise(promise, y, resolve, reject);
},
(err) => {
if (isCall) return;
isCall = true;
reject(err);
}
);
} else {
resolve(x);
}
} catch (error) {
if (isCall) return;
isCall = true;
reject(error);
}
} else {
resolve(x);
}
}
我们来看一下上面的代码:
第2行判断promise是否等于x,符合了Promise解决过程的第1条规则。
接着,我们先跳到33行,这里将x作为结果resolve出去,是不是很熟悉,就构造函数里面的resolve,所以这里就是链式调用中能拿到上一个then方法返回的值。
现在,我们到回第5行开始看,在x为对象或者函数时,设置一个isCall标识,目的是防止then被多次调用。然后将x.then赋值给then变量,如果为then为函数,则通过call该this指向为x,在传入两个函数(实际就是我们前面讲的then的onFulfilled和onRejected),以y为参数的函数如果被执行则递归调用resolutionPromise。以err为参数的函数如果被调用则reject返回错误原因。如果then不是函数则直接返回x。
到这里我们基本就完成了Promise的实现。
有人就会有疑问,Promise的解决过程的第2点的情况,好像没有实现?
是的真的没有吗?实际上是已经实现的,我们回到上面的代码第7行,可以看到我们通过trycatch包裹了我们的代码来实现的。这里如果x是Promise的话,那Promise必定是有then方法的(毕竟是我们自己实现的),那这样的话,就走到了then等于函数的判断逻辑中,这样我们就一次实现了两个逻辑(^▽^)。
到这里我们就真正的完整实现了Promise,那怎么验证我们写的没有问题呢?这里可以通过promises-aplus-tests测试工具来测试。在我们的代码中加上以下代码:
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = MyPromise;
然后在我们的当前文件的目录下,按顺序执行以下命令:
npm init -y
npm install promises-aplus-tests -D
npm set-script test "promises-aplus-tests MyPromise"
npm run test
那么我们就可以看到以下的输出结果:
看到上面的输出结果,恭喜你实现了一个自己的Promise了~~~
附上狗头保命
这里附上完整的代码,以便各位看官查验:
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function MyPromise(executor) {
this.value = null; // 记录异步任务执行成功的结果
this.reason = null; // 记录异步任务失败的结果
this.state = PENDING; // 异步任务的状态,当前为pending
const that = this;
this.fulfilledQueue = [];
this.rejectedQueue = [];
function resolve(value) {
if (that.state === PENDING) {
that.value = value;
that.state = FULFILLED;
that.fulfilledQueue.forEach((fn) => fn());
}
}
function reject(reason) {
if (that.state === PENDING) {
that.reason = reason;
that.state = REJECTED;
that.rejectedQueue.forEach((fn) => fn());
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
MyPromise.prototype.then = function (onFulfilled, onRejected) {
if (typeof onFulfilled !== "function") {
onFulfilled = (value) => value;
}
if (typeof onRejected !== "function") {
onRejected = (reason) => {
throw reason;
};
}
// 返回一个Promise对象且then多次调用
const p2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
const x = onFulfilled(this.value);
resolutionPromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
const x = onRejected(this.reason);
resolutionPromise(p2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.state === FULFILLED) {
// 异步执行结果
fulfilledMicrotask();
} else if (this.state === REJECTED) {
rejectedMicrotask();
} else {
// pending状态 注册异步任务队列
this.fulfilledQueue.push(fulfilledMicrotask);
this.rejectedQueue.push(rejectedMicrotask);
}
});
return p2;
};
function resolutionPromise(promise, x, resolve, reject) {
if (promise === x) {
return reject(new TypeError("循环引用报错"));
}
if (x !== null && (typeof x === "object" || typeof x === "function")) {
let isCall = false; // 标识then是否调用,避免重复调用
try {
let then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (isCall) return;
isCall = true;
resolutionPromise(promise, y, resolve, reject);
},
(err) => {
if (isCall) return;
isCall = true;
reject(err);
}
);
} else {
resolve(x);
}
} catch (error) {
// 这里漏了
if (isCall) return;
isCall = true;
reject(error);
}
} else {
resolve(x);
}
}
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = MyPromise;
参考资料:
Promise A+ 标准: promisesaplus.com/
Promise A+ 中文版: m.ituring.com.cn/article/665…