Step1:从构造函数开始
考虑以下三种场景,一般我们不会直接resolve,而是在Promise的构造函数执行异步任务,在异步任务回调时再执行resolve或者reject。又或者,在构造构造函数中通过throw抛出了异常。
// call resolve
new MyPromise((resolve, reject) => {
resolve("helloworld");
});
// call reject
new MyPromise((resolve, reject) => {
reject("111");
});
new MyPromise((resolve, reject) => {
throw new Error("test try catch");
});
上面三个Promise的状态分别从Pending转移到Fulfilled、Rejected、Rejected。
因此构造函数的逻辑就是:
- 接收一个函数
excutor作为参数 - 初始化Pending状态
- 通过
try...catch包裹excutor的执行,执行excutor时传入resolve和reject方法。 resolve、reject方法修改Promise的状态
因此,根据ES6的class语法,很容易写出这样的代码:
const PromiseStatus = {
Pending: "PENDING",
FulFilled: "FULFILLED",
Rejected: "REJECTED"
};
class MyPromise {
constructor(excutor) {
this.status = PromiseStatus.Pending;
this.value = undefined;
this.reason = undefined;
const resolve = (value) => {
setTimeout(() => {
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.FulFilled;
// 其他逻辑
}
});
};
const reject = (reason) => {
setTimeout(() => {
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.Rejected;
// 其他逻辑
}
});
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
}
这样的话,Step1就完成了。目前是可以通过构造函数生成一个Promise实例,但没有then方法,resolve、reject之后也没有执行其他逻辑。别急,我们一步一步来。
Step2: 简单的then方法
再来回顾一下我们一般怎么使用then方法?通常,我们会通过Promise去执行一个异步操作,然后通过then(callback)处理Promise返回的值。另外,还经常使用then的链式调用。再者,就是一个Promise可能会绑定多个then方法。
// 链式调用
new MyPromise((resolve) => {
resolve("a");
})
.then((val) => {
console.log(val);
return val + "b";
})
.then((val) => {
console.log(val);
return val + "c";
})
.then((val) => {
console.log(val);
});
// 同一个Promise,then可执行多次
const promise = new MyPromise((resolve) => {
resolve("a");
});
promise.then((val) => console.log("1:", val));
promise.then((val) => console.log("2:", val));
除了异步任务正常的逻辑,也不要忘了then方法是可以传入第二个参数的,它也是一个方法,接收一个参数为据因,当Promise执行异常时,会把据因传到这个方法中。
new MyPromise((resolve) => {
resolve("a");
})
.then((val) => {
throw new Error(val);
})
.then(
(val) => {
console.log("not run");
return "not run";
},
(error) => {
return "handle error";
}
)
.then((val) => {
console.log(val);
});
那么,我们来总结一下实现then方法的几个点:
- 支持链式调用,支持值透传
- 同一个Promise,
then可执行多次,当Promise的状态从Pending转化为Fulfilled或者Rejected时,依绑定的次序执行对应的回调。
在此之前,我们先定义一下then方法:
then(onFulfilled, onRejected): Promise<any>;
onFulfilled(x: any): any;
onRejected(x: any): any;
链式调用
调用then时,两个参数都是可选的,这时候,为了透传值,我们需要判断一下传入的两个参数是否为函数,如果不是,需要给它一个默认的处理方法:
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
}
onFulfilled的默认处理方式就是直接透传value,而onRejected就是继续把据因往外抛出。
链式调用实际上就是在每次执行then,都会返回一个新的Promise,那么我们在then的最后实例化一个promise2进行返回即可:
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
const promise2 = new MyPromise((resolve, reject) => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch(error) {
reject(error);
}
});
return promise2;
}
then支持多次调用
同一个Promise可以多次调用then去绑定不同的回调,那么肯定是需要一个数组去存储onFulfilled和onRejected。
对于then方法的执行,分为三种情况:
- Promise的状态为Pending,那么执行
then(onFulfilled, onRejected)时把onFulfilled和onRejected方法分别放入数组中,待Promise的状态转为Fulfilled或Rejected时,再依次执行。 - Promise的状态为Fulfilled,那么直接执行
onFulfilled方法。 - Promise的状态为Rejected,那么直接执行
onRejected方法。
基于以上的情况,首先我们增加两个数组去存储then方法传入的参数:
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
resolve和reject是转化Promise状态的方法,因此,在执行这两个参数时,分别遍历这两个数组,以此执行里面的回调函数,并且传入值或者据因,此时,构造函数应该变成了这个样子:
constructor(excutor) {
this.status = PromiseStatus.Pending;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
this.value = undefined;
this.reason = undefined;
// 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
const resolve = (value) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.FulFilled;
this.value = value;
setTimeout(() => {
this.onFulfilledCallbacks.forEach((callback) => {
callback(value);
});
});
}
};
// 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
const reject = (reason) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.Rejected;
this.reason = reason;
setTimeout(() => {
this.onRejectedCallbacks.forEach((callback) => {
callback(reason);
});
});
}
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
然后,基于执行then方法时的不同状态,我们在promise2的构造函数中添加以下逻辑,以区分不同状态执行不同的操作:
const promise2 = new MyPromise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
if (this.status === PromiseStatus.Pending) {
this.onFulfilledCallbacks.push(onFulfilledCallback);
this.onRejectedCallbacks.push(onRejectedCallback);
}
if (this.status === PromiseStatus.FulFilled) {
setTimeout(() => onFulfilledCallback(this.value));
}
if (this.status === PromiseStatus.Rejected) {
setTimeout(() => onRejectedCallback(this.reason));
}
});
return promise2;
你可能会注意到:onFulfilledCallback和onRejectedCallback是什么东西?它们是基于onFulfilled、onRejected的封装,为什么需要这个封装?我们来看以下这个例子:
new Promise((resolve, reject) => {
reject("a");
})
.then(
(val) => {
console.log("not run");
return "handle then";
},
(error) => {
console.log('error:', error);
return "handle error";
}
)
.then((val) => {
console.log(val);
});
// error: a
// handler error
可以看到,onRejected处理完之后,then方法所返回的Promise的状态是Fulfilled,所以最后一个then输出了handle error的逻辑。实际上,只有onFulfilled、onRejected抛出了错误,返回的Promise才从Pending转化到Rejected,否则,会转化到Fulfilled状态。因此,onFulfilledCallback和onRejectedCallback的实现如下:
let _resolve;
let _reject;
const onFulfilledCallback = (value) => {
try {
const x = onFulfilled(value);
_resolve(x);
} catch (error) {
_reject(error);
}
};
const onRejectedCallback = (value) => {
try {
const x = onRejected(value);
_resolve(x);
} catch (error) {
_reject(error);
}
};
_resolve和_reject方法在promise2实例化时得到赋值,因此,目前实现的Promise完整代码应该是这样的:
class MyPromise {
constructor(excutor) {
this.status = PromiseStatus.Pending;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
this.value = undefined;
this.reason = undefined;
// 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
const resolve = (value) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.FulFilled;
this.value = value;
setTimeout(() => {
this.onFulfilledCallbacks.forEach((callback) => {
callback(value);
});
});
}
};
// 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
const reject = (reason) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
this.status = PromiseStatus.Rejected;
this.reason = reason;
setTimeout(() => {
this.onRejectedCallbacks.forEach((callback) => {
callback(reason);
});
});
}
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
let _resolve;
let _reject;
const onFulfilledCallback = (value) => {
try {
const x = onFulfilled(value);
_resolve(x);
} catch (error) {
_reject(error);
}
};
const onRejectedCallback = (value) => {
try {
const x = onRejected(value);
_resolve(x);
} catch (error) {
_reject(error);
}
};
const promise2 = new MyPromise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
if (this.status === PromiseStatus.Pending) {
this.onFulfilledCallbacks.push(onFulfilledCallback);
this.onRejectedCallbacks.push(onRejectedCallback);
}
if (this.status === PromiseStatus.FulFilled) {
setTimeout(() => onFulfilledCallback(this.value));
}
if (this.status === PromiseStatus.Rejected) {
setTimeout(() => onRejectedCallback(this.reason));
}
});
return promise2;
}
}
再次执行测试代码,结果和预期一致:
new MyPromise((resolve) => {
resolve("a");
})
.then((val) => {
console.log(val);
return val + "b";
})
.then((val) => {
console.log(val);
return val + "c";
})
.then((val) => {
console.log(val);
});
// a
// ab
// abc
const promise = new MyPromise((resolve) => {
resolve("a");
});
promise.then((val) => console.log("1:", val));
promise.then((val) => console.log("2:", val));
// 1:a
// 2:a
但是,如果我们在then中返回一个Promise,又或者在resolve中返回一个Promise,那么,输出的结果就和预期不一致了:
new MyPromise((resolve) => {
resolve("a");
})
.then((val) => {
return new MyPromise((resolve) => resolve(val));
})
.then((val) => {
console.log("is promise, not a:", val);
});
预期的结果应该是is a,但是我们目前却得到了is promise, not a的输出。主要是因为我们目前实现的Promise并不支持resolvePromise的过程,这部分是实现符合Promise A+规范的Promise中最难以理解的部分,接下来我们就来细说如何理解resolvePromise,而不是死记硬背。
Step3: 实现resolvePromise
理解为什么有resolvePromise
在写resolvePromise之前,我们需要理解为什么需要这个过程。从上面的例子来看,如果我们从then中显式返回一个Promise,那么,由于原本我们的then方法就返回一个Promise对象,如果不处理,那么就会造成,返回的结果是Promise的嵌套,所以才会打印is promise, not a的结果。
从PromiseA+的规范上来说就是:假设then返回的值为x,那么,x有可能是一个Promise对象,或者是一个thenable对象(拥有then方法的对象或函数)。因此需要通过resolvePromise去获得x的最终状态,或者说最终值。
先来看下这个例子:
new Promise((resolve) => {
resolve();
})
.then(() => {
// 做了一堆逻辑
return new Promise((resolve) => {
// 做了一堆逻辑
setTimeout(() => {
resolve("final");
}, 1000);
});
})
.then((val) => {
console.log("val:", val);
});
第一个then方法中onFulfilled方法显式返回了一个Promise对象,我们把它称为pA。但是,我们实现then方法时知道,then方法会返回一个Promise对象,我们成为pB。此时pA和pB的状态应该是这样的:pB(pA)。
如果不通过resolvePromise处理,假如我们需要在第二个then中继续处理pA的状态,那么就会出现“嵌套Promise"的存在:
new Promise((resolve) => {
resolve();
})
.then(() => {
// 做了一堆逻辑
return new Promise((resolve) => {
// 做了一堆逻辑
setTimeout(() => {
resolve("final");
}, 1000);
});
})
.then((pA) => {
pA.then(() => {
return new Promise((resolve) => {
// 做了一堆逻辑
setTimeout(() => {
resolve("final");
}, 1000);
});
}).then((pA2) => {
pA2.then(() => {
return new Promise((resolve) => {
// 做了一堆逻辑
setTimeout(() => {
resolve("final");
}, 1000);
});
}).then(pA3 => {
pA3.then(() => {
return new Promise((resolve) => {
// 做了一堆逻辑
setTimeout(() => {
resolve("final");
}, 1000);
});
})
}).catch(() => {
console.log('error');
})
})
.catch(() => {
console.log('error');
});
假设我们需要依次读取文件,每个文件都依赖上一个文件的内容,那么Promise的链式调用就会变成恐怖的“死亡回调”:
readFile('./file1.txt')
.then((val) => {
return readFile(val);
})
.then(promise => {
promise.then(val => {
return readFile(val);
}).then(promise => {
promise.then(val => {
return readFile(val);
}).then(promise => {
promise.then(val => {
console.log('套不下去了。。。。');
});
});
});
});
这样看上去实际上是不必要的,因为如果我们加上异常处理的逻辑,这跟以前的死亡嵌套并没有任何区别。而我的理解是:resolvePromise解决了这种问题。
执行then方法返回一个Promise,记为promiseA,如果onFulfilled方法或者onRejected方法返回一个Promise,记为promiseB,那么,promiseA的状态取决于promiseB,也就是说如果promiseB的状态为fulfilled,那么promiseA的状态也为fullfiled,如果promiseB的状态为rejected,那么promiseA的状态也为rejected。同时,promiseB的最终值也为promiseA的最终值。
简单来说就是:promiseA的状态控制转移到了promiseB。从另一个方面理解就是:promiseB中的过程被resolvePromise“解构”了。假设最终值为Y,pX为Promise,那么:
Y = resolvePromise(pA(pB(pC(pD(pE(Y))))))
这样的话,上面的例子就可以变成:
readFile('./file1.txt')
.then(val => readFile(val))
.then(val => readFile(val))
.then(val => readFile(val))
.then(val => readFile(val))
.then(val => readFile(val))
.then(val => readFile(val))
.then(val => readFile(val))
一目了然多清晰啊!好了,说了那么多,开始实现resolvePromise了。
实现 resolvePromise
resolvePromise方法需要传入四个参数:promise、x、resolve、reject,其中promise为then方法返回的Promise实例,resolve和reject为该Promise实例的方法,x为执行onFulfilled或者onRejected的返回值。
resolvePromise主要处理以下三点:
- 禁止循环调用
- 如果 x 是一个Promise实例
- 如果 x 是一个 thenable 对象
首先,禁止循环调用,也就是判断promise和x是否为同一个对象:
const resolvePromise = (promise, x, resolve, reject) => {
// 禁止循环调用
if (promise === x) {
reject(new TypeError("禁止循环调用"));
}
};
然后就是判断是否为Promise实例,如果是Promise实例并且正处于Pending状态,那么等待x的状态发生变化,得到这个Promise执行后的值y。因为y也有可能是thenable对象或者Promise实例,因此,需要对y进行resolvePromise的调用。如果Promise 的状态已经是fulfilled或者rejected,那么直接调用x.then即可。
const resolvePromise = (promise, x, resolve, reject) => {
// 禁止循环调用
if (promise === x) {
reject(new TypeError("禁止循环调用"));
}
// 如果 x 是 Promise 实例
if (x instanceof MyPromise) {
// 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
if (x.status === PromiseStatus.Pending) {
x.then(
(y) => {
// 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
resolvePromise(promise, y, resolve, reject);
},
(r) => {
reject(r);
}
);
} else {
x.then(resolve, reject);
}
}
};
然后继续判断x是否为thenable对象,如果是,则以x为x.then方法的this调用,得到值y,跟前面Promise的处理方式一样,y是有可能为Promise实例或者thenable对象,因此也需要进行resolvePromise的处理。如果x不为thenable对象,那么直接resolve(x)即可。
主要注意的是,处理thenable对象和处理Promise对象的方式有点像,但是,因为Promise实例的状态只要一切换就不会再执行,因此不需要担心重复执行的问题。但是thenable对象并没有这个限制,因此还需要添加一个辅助变量判断是否已经执行过x.then,如果已经执行过,则忽略。
const resolvePromise = (promise, x, resolve, reject) => {
// 禁止循环调用
if (promise === x) {
reject(new TypeError("禁止循环调用"));
}
// 如果 x 是 Promise 实例
if (x instanceof MyPromise) {
// 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
if (x.status === PromiseStatus.Pending) {
x.then(
(y) => {
// 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
resolvePromise(promise, y, resolve, reject);
},
(r) => {
reject(r);
}
);
} else {
x.then(resolve, reject);
}
} else {
// x 不是 Promise 实例
// x 是函数或对象
if (typeof x === "function" || (x !== null && typeof x === "object")) {
let called = false;
try {
const then = x.then;
// 确保只执行一次
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) {
return;
}
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) {
return;
}
called = true;
reject(r);
}
);
} else {
// 是一个对象/函数,但不是thenable,直接resolve即可
resolve(x);
}
} catch (error) {
if (called) {
return;
}
called = true;
reject(error);
}
} else {
// 不是Promise、thenable对象,直接resolve即可
resolve(x);
}
}
};
就这样,完整的resolvePromise也就执行完毕。
Final: 最终实现,并通过测试
接合Step1、Step2、Step3,最终实现的Promise代码如下:
const PromiseStatus = {
Pending: "PENDING",
FulFilled: "FULFILLED",
Rejected: "REJECTED"
};
class MyPromise {
constructor(excutor) {
this.status = PromiseStatus.Pending;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
this.value = undefined;
this.reason = undefined;
// 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
const resolve = (value) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
setTimeout(() => {
this.status = PromiseStatus.FulFilled;
this.value = value;
this.onFulfilledCallbacks.forEach((callback) => {
callback(value);
});
});
}
};
// 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
const reject = (reason) => {
// promise 的状态只能更改一次
if (this.status === PromiseStatus.Pending) {
setTimeout(() => {
this.status = PromiseStatus.Rejected;
this.reason = reason;
this.onRejectedCallbacks.forEach((callback) => {
callback(reason);
});
});
}
};
try {
excutor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value;
onRejected =
typeof onRejected === "function"
? onRejected
: (reason) => {
throw reason;
};
let promise2;
let _resolve;
let _reject;
const resolvePromise = (promise, x, resolve, reject) => {
// 禁止循环调用
if (promise === x) {
reject(new TypeError("禁止循环调用"));
}
// 如果 x 是 Promise 实例
if (x instanceof MyPromise) {
// 如果 x 的状态为 Pending,那么直到 x 为 Fulfilled 或 Rejected 才调用 resolve
if (x.status === PromiseStatus.Pending) {
x.then(
(y) => {
// 进一步 resolvePromise 是因为 y 也有可能是个 Promise 实例 / thenable 对象
resolvePromise(promise, y, resolve, reject);
},
(r) => {
reject(r);
}
);
} else {
x.then(resolve, reject);
}
} else {
// x 不是 Promise 实例
// x 是函数或对象
if (typeof x === "function" || (x !== null && typeof x === "object")) {
let called = false;
try {
const then = x.then;
// 确保只执行一次
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) {
return;
}
called = true;
resolvePromise(promise, y, resolve, reject);
},
(r) => {
if (called) {
return;
}
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (error) {
if (called) {
return;
}
called = true;
reject(error);
}
} else {
resolve(x);
}
}
};
const onFulfilledCallback = (value) => {
try {
const x = onFulfilled(value);
resolvePromise(promise2, x, _resolve, _reject);
} catch (error) {
_reject(error);
}
};
const onRejectedCallback = (value) => {
try {
const x = onRejected(value);
resolvePromise(promise2, x, _resolve, _reject);
} catch (error) {
_reject(error);
}
};
promise2 = new MyPromise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
if (this.status === PromiseStatus.Pending) {
this.onFulfilledCallbacks.push(onFulfilledCallback);
this.onRejectedCallbacks.push(onRejectedCallback);
}
if (this.status === PromiseStatus.FulFilled) {
setTimeout(() => onFulfilledCallback(this.value));
}
if (this.status === PromiseStatus.Rejected) {
setTimeout(() => onRejectedCallback(this.reason));
}
});
return promise2;
}
}
最后通过promises-aplus-tests这个测试工具进行测试,结果表明:通过872个用例,符合Promise A+规范!
