写一个Promise

202 阅读8分钟

今儿个咱们来实现一个符合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);

image.png
可以看到状态更新成功,并且外部对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指向xresolvePromiserejectPromise之间仅一次调用机会,先到先得,在这次机会使用之后,不再处理抛出异常,我们使用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所在的事件队列末尾");

image.png
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);
});

image.png 在测试一下当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);
            }
        });
    }
}