今儿个咱们来实现一个符合Promises/A+规范的Promise,规范原文地址:promisesaplus.com/
OK,开始吧,咱们先来看规范的第一部分:术语。
1.术语
1.1. “promise”是一个拥有then方法的对象或函数,其行为符合本规范。
1..2 “thenable”是一个定义了then方法的对象或函数。
1.3. “value”是一个合法的 JavaScript 值,可以是任何类型(包括undefined、thenable 或 promise)。
1.4. “exception”是使用throw语句抛出的值。
1.5. “reason”是表示承诺被拒绝的原因的值。
术语部分我们可以得到如下信息:
Promise是一个对象或者函数,拥有一个叫做then的对象或者函数、一个value、一个reason。
我们使用class来实现它:
class Promise {
value = undefined;
reason = undefined;
constructor(){
}
then(){
}
}
2.要求
2.1 promise状态
promise必须处于以下三种状态之一:pending(挂载)、fulfilled(完成) 或者rejected(拒绝)。
2.1.1. pending时:
2.1.1.1. 可以过渡到fulfilled或者rejected状态。
2.1.2. fulfilled时:
2.1.2.1. 不得过渡到任何其他状态。
2.1.2.2. 必须有一个不可改变的值。
2.1.3. rejected时:
2.1.3.1. 不得过渡到任何其他状态。
2.1.3.2. 必须有一个不可改变的理由。
这里,“不可改变”意味着恒等(即`===`),但并不意味着深层次的不变性.
总结一下:promise的状态只有在pending时可更改,更改后不可逆,最终结果的value或者reason不可更改,但仅限于地址不可更改,意思是如果值为Object等引用类型,还是可以修改其属性值的。
那现在我们加一个状态status,分别用于将状态更新到fulfilled和rejected的resolve和reject方法,在这两个方法中,value和reason一旦被赋值,冻结当前对象,阻止值再次被修改,一个入口excutor执行方法,使用者在excutor可以调用resolve、reject执行状态更新。
status = "pending";
constructor(excutor){
let resolve = (value) => {
if(this.status === "pending"){
this.status = "fulfilled";
this.value = value;
Object.freeze(this);
}
}
let reject = (reason) =>{
if(this.status === "pending"){
this.status = "rejected";
this.reason = reason;
Object.freeze(this);
}
}
try {
excutor(resolve, reject);
} catch(e) {
// 执行出错时,拒绝该promise
reject(e);
}
}
现在我们来试用一下:
const test1 = new Promise(resolve => {
resolve("这里会调用Promise的resolve将状态更新为fulfilled");
});
test1.value = 1;
const test2 = new Promise((resolve, reject) => {
reject("这里会调用Promise的reject将状态更新为rejected");
});
test2.reason = 2;
console.log(test1, test2);
可以看到状态更新成功,并且外部对value和reason的值无法修改。
2.2 then方法
这部分内容比较多,咱们拆分一下,一块一块来。
promise必须提供一个then方法来访问其当前或最终值或原因,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完成后调用它,并将value作为它的第一个参数。
2.2.2.2. 在promise完成之前不能调用它。
2.2.2.3. 它不能被多次调用。
2.2.3. 如果onRejected是函数,
2.2.3.1. 必须在promise被拒绝后调用它,以reason作为它的第一个参数。
2.2.3.2 在promise被拒绝之前不能调用它。
2.2.3.3 它不能被多次调用。
总结:then方法接收onFulfilled、onRejected两个方法作为完成/拒绝后的回调,他们的第一个参数分别为value和reason.
2.2.1的意思是当promise处于pending状态时,onFulfilled、onRejected两个方法并不会在then中立即执行,而是会在resolve/reject更新状态后执行,所以在状态为pending时我们需要将他们暂时保存起来。
现在我们来完善then、resolve和reject:
let resolve = (value) => {
if(this.status === "pending"){
this.status = "fulfilled";
this.value = value;
Object.freeze(this);
// 执行回调
if(typeof this.onFulfilled === "function"){
this.onFulfilled(this.value);
}
}
}
let reject = (reason) =>{
if(this.status === "pending"){
this.status = "rejected";
this.reason = reason;
Object.freeze(this);
// 执行回调
if(typeof this.onRejected === "function"){
this.onRejected(this.reason);
}
}
}
then(onFulfilled, onRejected){
if(this.status === "pending"){
// pending状态下不会执行回调,故将回调保存,在状态更改后执行
// 这里暂时没有判断是否为函数,因为在resolve/reject里调用前会哦按段
this.onFulfilled = onFulfilled;
this.onRejected = onRejected;
}
// 如果已经是非pending状态,直接执行相应回调
else if(this.status === "fulfilled"){
console.log("then")
if(typeof onFulfilled === "function"){
onFulfilled(this.value);
}
} else if(this.status === "rejected"){
if(typeof onFulfilled === "function"){
onRejected(this.reason);
}
}
}
继续往下:
2.2.4. onFulfilled或者onRejected在[执行上下文]堆栈中仅包含平台代码之前不得调用
这一条的意思是,为了确保onFulfilled或者onRejected是异步执行的,他们俩不在then调用所在的事件队列中执行,可以使用微任务(例如MutationObserver)或者宏任务(例如(setTimeout)来实现。
我们这里选择setTimeout,在调用onFulfilled或者onRejected的地方包裹一层setTimeout:
setTimeout(() => {
onFulfilled(this.value);
});
2.2.6. then可以在同一个promise上多次调用。
2.2.6.1. promise完成时,所有的onFulfilled回调必须按照它们相应的then的原始顺序执行。
2.2.6.2. promise被拒绝时,所有的onRejected回调必须按照它们相应的then的原始顺序执行。
和上边一样,如果promise处于fulfilled或者rejected状态,当前then调用中相应的onFulfilled/onRejected是会立即执行的,不需要考虑then的顺序,需要考虑顺序的,只有在promise状态为pending时,因为这时候回调不会执行,而是在状态更改后在resolve/reject中去执行,这时候才需要按照then的顺序去执行回调。那我们就用数组来保存回调,pending状态下每次调用then就将回调push到数组,执行时再按顺序取出来执行。
// then中:在pending状态下将回调推入数组
if(this.status === "pending"){
if(typeof onFulfilled === "function"){
this.onFulfilledList.push(onFulfilled);
}
if(typeof onRejected === "function"){
this.onRejectedList.push(onRejected);
}
}
// resolve:修改onFulfilled调用
setTimeout(() => {
this.onFulfilledList.forEach((fn) => fn(this.value));
});
// reject:修改onRejected调用
setTimeout(() => {
this.onRejectedList.forEach(fn => fn(this.reason));
});
2.2.7. then必须返回一个promise。
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.1. 如果onFulfilled或onRejected返回值x,则运行promise解析过程[[Resolve]](promise2, x)。
2.2.7.2 如果onFulfilled或onRejected抛出异常e,则promise2必须以e为理由拒绝。
2.2.7.3. 如果onFulfilled不是函数并且promise1完成,则promise2必须用与promise1相同的值来完成。
2.2.7.4. 如果onRejected不是函数并且promise1被拒绝,则promise2必须以与 promise1相同的原因被拒绝。
这里的意思很明显了,就是为了实现链式调用。如果onFulfilled/onRejected是函数,执行时出错则拒绝promise2,onFulfilled/onRejected拿到返回值后运行promise解析,如果不是函数,则以当前promise的value/reason来完成或拒绝promise2。
promise解析过程方法我们取名promiseResolution,修改所有调用onFulfilled/onRejected的地方,resolve/reject中的调用,我们只在then中推入回调时去处理:
then(onFulfilled, onRejected){
const promise2 = new Promise((resolve, reject) => {
if(this.status === "pending"){
this.onFulfilledList.push(() => {
if(typeof onFulfilled === "function"){
try {
let x = onFulfilled(this.value);
promiseResolution(promise2, x);
} catch(e){
reject(e);
}
} else {
resolve(this.value);
}
});
this.onRejectedList.push(() => {
if(typeof onRejected === "function"){
try {
let x = onRejected(this.reason);
promiseResolution(promise2, x);
} catch(e){
reject(e);
}
} else {
reject(this.reason);
}
});
} else if(this.status === "fulfilled"){
setTimeout(() => {
if(typeof onFulfilled === "function"){
try {
let x = onFulfilled(this.value);
promiseResolution(promise2, x);
} catch(e){
reject(e);
}
} else {
resolve(this.value);
}
});
} else if(this.status === "rejected"){
setTimeout(() => {
if(typeof onFulfilled === "function"){
try {
let x = onRejected(this.reason);
promiseResolution(promise2, x);
} catch(e){
reject(e);
}
} else {
reject(this.reason);
}
});
}
});
// promise解析
function promiseResolution(promise, x){
}
return promise2;
}
重复代码过多,精简一下:
then(onFulfilled, onRejected){
// 执行回调
const excutorCb = (cb, valueKey, resolve, reject) => {
const value = this[valueKey];
setTimeout(() => {
if(typeof cb === "function"){
try {
const x = cb(value);
promiseResolution(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
} else {
if(valueKey === "value"){
resolve(value);
} else if(valueKey === "reason"){
reject(value);
}
}
});
}
const promise2 = new Promise((resolve, reject) => {
if(this.status === "pending"){
this.onFulfilledList.push(() => {
excutorCb(onFulfilled, "value", resolve, reject);
});
this.onRejectedList.push(() => {
excutorCb(onRejected, "reason", resolve, reject);
});
}
else if(this.status === "fulfilled"){
excutorCb(onFulfilled, "value", resolve, reject);
}
else if(this.status === "rejected"){
excutorCb(onRejected, "reason", resolve, reject);
}
});
// promise解析
function promiseResolution(promise, x){
}
return promise2;
}
2.3 promise解析过程
注意啊注意啊,最难的部分来啦!
要运行[[Resolve]](promise, x),请执行以下步骤:
2.3.1. 如果promise和x引用同一个对象,则以TypeError为理由拒绝promise。
这一条还是很好理解滴:
function promiseResolution(promise, x){
if(promise === x) {
reject(new TypeError("循环啦循环啦!"));
}
}
2.3.2. 如果x是promise,则采用其状态:
2.3.2.1. 如果x处于挂起状态,则promise必须保持挂起状态,直到x完成或被拒绝。
2.3.2.2. x完成时,promise以相同的值完成。
2.3.2.3. x被拒绝,promise以同样的理由拒绝
2.3.2.1 的意思是如果x在挂起状态,那我们需要停止当前解析,等x有了结果用该结果来再次解析,那么我们怎么知道x啥时候有结果呢?调用它的then方法,在onFulfilled中使用结果值y再次执行解析,x被拒绝时不需要执行解析过程,所以这里直接用reject作为onRejected回调:
function promiseResolution(promise, x, resolve, reject){
if(promise === x) {
reject(new TypeError("循环啦循环啦!"));
} else if(x instanceof Promise){
if(x.status === "pending") {
x.then(y => {
promiseResolution(promise, y, resolve, reject);
}, reject);
} else if(x.status === "fulfilled"){
resolve(x.value);
} else if(x.status === "rejected"){
reject(x.reason);
}
}
}
2.3.3. 如果x是一个对象或函数,
2.3.3.1. then = x.then。
2.3.3.2. 如果获取属性x.then抛出异常e,用e作为拒绝promise的原因。
2.3.4. 如果x不是一个对象或函数,用x为值完成promise。
这里咱们先把2.3.4 提到前边来完成。
if(x !== null && /^(object|function)$/.test(typeof x)){
try {
const then = x.then;
} catch(e) {
reject(e);
}
} else {
resolve(x);
}
OK,继续2.3.3:
2.3.3.3. 如果then是一个函数,用x作为函数的this,第一个参数为resolvePromise和第二个参数为rejectPromise,其中:
2.3.3.3.1. 如果resolvePromise调用时参数为y,则运行[[Resolve]](promise, y)。
2.3.3.3.2. 如果rejectPromise调用时参数为r,以r为理由拒绝promise。
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. 否则,以e为原因拒绝promise。
2.3.3.4. 如果then不是一个函数,用x为值完成promise。
再次把2.3.3.4提到前边处理。
如果then是函数,我们使用call将它的this指向x,resolvePromise和rejectPromise之间仅一次调用机会,先到先得,在这次机会使用之后,不再处理抛出异常,我们使用isCalled来记录这次机会是否还在。
const then = x.then;
let isCalled = false;
if(typeof then === "function"){
try {
let resolvePromise = (y) => {
if(isCalled) return;
isCalled = true;
promiseResolution(promise, y, resolve, reject);
}
let rejectPromise = (r) => {
if(isCalled) return;
isCalled = true;
reject(r);
}
then.call(x, resolvePromise, rejectPromise);
} catch(e) {
if(isCalled) return;
reject(e);
}
} else {
resolve(x);
}
最后再将这里的try/catch与2.3.3.2的中和一下,promiseResolution完整代码:
function promiseResolution(promise, x, resolve, reject){
if(promise === x) {
reject(new TypeError("循环啦循环啦!"));
} else if(x instanceof Promise){
if(x.status === "pending") {
x.then(y => {
promiseResolution(promise, y, resolve, reject);
}, reject);
} else if(x.status === "fulfilled"){
resolve(x.value);
} else if(x.status === "rejected"){
reject(x.reason);
}
} else {
if(x !== null && /^(object|function)$/.test(typeof x)){
let isCalled = false;
try {
const then = x.then;
if(typeof then === "function"){
let resolvePromise = (y) => {
if(isCalled) return;
isCalled = true;
promiseResolution(promise, y, resolve, reject);
}
let rejectPromise = (r) => {
if(isCalled) return;
isCalled = true;
reject(r);
}
then.call(x, resolvePromise, rejectPromise);
} else {
resolve(x);
}
} catch(e) {
if(isCalled) return;
reject(e);
}
} else {
resolve(x);
}
}
}
到这里一个Promise就写好啦,咱们来测试一下。
测试
首先是完成、拒绝以及链式调用:
const test1 = new Promise(resolve => {
resolve("result1");
})
.then(result => {
console.log(`收到一个结果:${result}`);
return "result2";
})
.then(result => {
console.log(`收到一个结果:${result}`);
});
const test2 = new Promise((resolve, reject) => {
reject("reason");
})
.then(null, reason => {
console.log(`被拒绝啦,居然是因为:${reason}`)
});
console.log("这里是then所在的事件队列末尾");
OK,没问题,再来测试一下x是函数:
const test1 = new Promise(resolve => {
resolve();
})
.then(result => {
return new Promise(resolve => {
resolve(1);
});
})
.then(result => {
console.log("这里应该接收到上一个then调用中返回的Promise的结果:", result);
});
const test2 = new Promise(resolve => {
resolve();
})
.then(result => {
return new Promise((resolve, reject) => {
reject(2);
});
})
.then(null, reason => {
console.log("这里应该接收到上一个then调用中返回的Promise的原因:", reason);
});
在测试一下当x为对象时:
const test1 = new Promise(resolve => {
resolve();
})
.then(result => {
return { name: "testName"};
})
.then(result => {
// 上一个then调用返回的结果对象then属性不是个方法
// 这里应该接收到的是这个对象本身
console.log(result);
});
const test2 = new Promise(resolve => {
resolve("result of test2");
})
.then(result => {
return {
name: "testName",
then(resolvePromise, rejectPromise){
resolvePromise("from then resolvePromise");
// rejectPromise("from then rejectPromise");
}
};
})
.then(result => {
// 上一个then调用返回的结果对象then属性是个方法
// 这里应该接收到的是这个then方法中resolvePromise接收到的值的值"from then resolvePromise"
console.log(result);
});
看起来暂时没啥问题。
到这里,按照规范来说是已经完成了,我们还可以参照ES6的Promise实现,来实现一些常用的静态方法。
catch和其他静态方法
catch(onRejected) {
this.then(null, onRejected);
}
// 立即完成
static resolve(value){
return new Promise(resolve => {
resolve(value);
});
}
// 立即拒绝
static reject(reason){
return new Promise((resolve, reject) => {
reject(reason);
});
}
static all(promises){
return new Promise((resolve, reject) => {
try {
const results = [];
let resolvedCount = 0;
promises.forEach((x, i) => {
if(x instanceof Promise){
x.then(value => {
results[i] = value;
resolvedCount++;
if(resolvedCount === promises.length){
resolve(results);
}
}, error => {
reject(error);
});
} else {
results[i] = x;
resolvedCount++;
if(resolvedCount === promises.length){
resolve(results);
}
}
});
} catch(e) {
reject(e);
}
});
}
完整代码
最后奉上完整代码,代码地址github.com/FlowerLiao/… :
class Promise {
status = "pending";
value = undefined;
reason = undefined;
onFulfilledList = [];
onRejectedList = [];
constructor(excutor){
let resolve = (value) => {
if(this.status === "pending"){
this.status = "fulfilled";
this.value = value;
Object.freeze(this);
this.onFulfilledList.forEach((fn) => fn(this.value));
}
};
let reject = (reason) =>{
if(this.status === "pending"){
this.status = "rejected";
this.reason = reason;
Object.freeze(this);
this.onRejectedList.forEach(fn => fn(this.reason));
}
};
try {
excutor(resolve, reject);
} catch(e){
reject(e);
}
}
then(onFulfilled, onRejected){
// 执行回调
const excutorCb = (cb, valueKey, resolve, reject) => {
setTimeout(() => {
const value = this[valueKey];
if(typeof cb === "function"){
try {
const x = cb(value);
promiseResolution(promise2, x, resolve, reject);
} catch(e){
reject(e);
}
} else {
if(valueKey === "value"){
resolve(value);
} else if(valueKey === "reason"){
reject(value);
}
}
});
}
// promise解析
const promiseResolution = (promise, x, resolve, reject) => {
if(promise === x) {
reject(new TypeError("循环啦循环啦!"));
} else if(x instanceof Promise){
if(x.status === "pending") {
x.then(y => {
promiseResolution(promise, y, resolve, reject);
}, reject);
} else if(x.status === "fulfilled"){
resolve(x.value);
} else if(x.status === "rejected"){
reject(x.reason);
}
} else {
if(x !== null && /^(object|function)$/.test(typeof x)){
let isCalled = false;
try {
const then = x.then;
if(typeof then === "function"){
let resolvePromise = (y) => {
if(isCalled) return;
isCalled = true;
promiseResolution(promise, y, resolve, reject);
}
let rejectPromise = (r) => {
if(isCalled) return;
isCalled = true;
reject(r);
}
then.call(x, resolvePromise, rejectPromise);
} else {
resolve(x);
}
} catch(e) {
if(isCalled) return;
reject(e);
}
} else {
resolve(x);
}
}
}
const promise2 = new Promise((resolve, reject) => {
if(this.status === "pending"){
this.onFulfilledList.push(() => {
excutorCb(onFulfilled, "value", resolve, reject);
});
this.onRejectedList.push(() => {
excutorCb(onRejected, "reason", resolve, reject);
});
}
else if(this.status === "fulfilled"){
excutorCb(onFulfilled, "value", resolve, reject);
}
else if(this.status === "rejected"){
excutorCb(onRejected, "reason", resolve, reject);
}
});
return promise2;
}
catch(onRejected) {
this.then(null, onRejected);
}
static resolve(value){
return new Promise(resolve => {
resolve(value);
});
}
static reject(reason){
return new Promise((resolve, reject) => {
reject(reason);
});
}
static all(promises){
return new Promise((resolve, reject) => {
try {
const results = [];
let resolvedCount = 0;
promises.forEach((x, i) => {
if(x instanceof Promise){
x.then(value => {
results[i] = value;
resolvedCount++;
if(resolvedCount === promises.length){
resolve(results);
}
}, error => {
reject(error);
});
} else {
results[i] = x;
resolvedCount++;
if(resolvedCount === promises.length){
resolve(results);
}
}
});
} catch(e) {
reject(e);
}
});
}
}