抽丝剥茧手写Promise

195 阅读8分钟

手写参考PromiseA+规范

都已经9021年了,你该学会手写一个Promise了,快来看看吧。

手写基础的Promise

第一步 初写简单的Promise

tips: 我们可以先用typescript来看一下原生的Promise方法

看一下PromiseA+规范

1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise).
image.png 1.4 “exception” is a value that is thrown using the throw statement.

1.5 “reason” is a value that indicates why a promise was rejected.

image.png

2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
2.2 A promise must provide a then method to access its current or eventual value or reason.
A promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected)

微信图片编辑_20210728171403.jpg

通过上述,我们能发现

  • 每个 Promise 都有三个状态: pending 表示为等待态、fulfilled 表示为成功态、rejected 表示变为失败态,并且状态不可逆
  • 原生的 Promise 使用 new 方法使用的,说明它是一个类(也可以写成函数)
  • 它传入了一个 executor,它包括了两个参数:resolvereject
  • 每个 Promise 都有一个 then 方法,有两个可选参数,一个成功的回调onFulfilled,一个失败的回调onRejected
  • Promise 抛出异常后,走的是失败态
// 枚举三个状态
const enum STATUS {
    pending = 'PENDING',
    fulfilled = 'FULFILLED',
    rejected = 'REJECTED'
}
class Promise {
    status: STATUS
    value: any
    reason: any
    constructor(executor:any){
        this.status = STATUS.pending; // 当前默认状态
        this.value = undefined; // 成功原因
        this.reason = undefined; // 失败原因
        const resolve = (value?:any) =>{
            if(this.status === STATUS.pending){
                this.status = STATUS.fulfilled;
                this.value = value;
            }
        }
        const reject = (reason?:any) =>{
            if(this.status === STATUS.pending){
                this.status = STATUS.rejected;
                this.reason = reason;
            }
        }
        try{
            executor(resolve,reject);
        }catch(e){
            // 当抛出异常时,走reject
            reject(e);
        }
    }
    // 每个 Promise 都有一个 then 方法,有两个可选参数一个成功的回调,一个失败的回调。
    then(onFulfilled?:any,onRejected?:any){
        if(this.status == STATUS.fufilled){
            onFulfilled(this.value);
        }
        if(this.status == STATUS.rejected){
            onRejected(this.reason);
        }
    }
}

第二步 then方法的改造

当代码改变下

let promise = new Promise((resolve,rejected)=>{
    setTimeout(()=>{
        resolve('ok')
    },1000)
})
promise.then((data)=>{
    console.log('ok')
},(err)=>{
    console.log('err')
})

我们发现不执行了,因为执行then的时候状态还是pending,所以不执行了。

我们再去看一下规范
2.2.6 then may be called multiple times on the same promise.
2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.
2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.

从文档中我们得知then可以被多次调用,并且如果是fulfilled会按顺序一个一个调用onFulfilled,如果是rejected会按顺序一个一个调用onRejected。这不就是之前说过的发布订阅的模式,先把所有的then方法按照onFulfilledonRejected存为两个数组,然后最后依次执行。我们就改造一下我们之前的代码

class Promise {
    status: STATUS
    value: any
    reason: any
    onFulfilledCallbacks:Function[]
    onRejectedCallbacks:Function[]
    constructor(executor:resolve(value?:any)=>void,reject:(reason?:any)=>void){
        this.status = STATUS.pending;
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = []
        const resolve = (value?:any) =>{
            if(this.status === STATUS.pending){
                this.status = STATUS.fulfilled;
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn=>fn());
            }
        }
        const reject = (reason?:any) =>{
            if(this.status === STATUS.pending){
                this.status = STATUS.rejected;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn=>fn());
            }
        }
        try{
            executor(resolve,reject);
        }catch(e){
            reject(e);
        }
    }
    then(onFulfilled?:any,onRejected?:any){
        if(this.status == STATUS.fulfilled){
            onFulfilled(this.value);
        }
        if(this.status == STATUS.rejected){
            onRejected(this.reason);
        }
        if(this.status == STATUS.pending){
            this.onFulfilledCallbacks.push(onFulfilled(this.value));
            this.onRejectedCallbacks.push(onRejected(this.reason));
        }
    }
}

继续看文档
2.2.1 Both onFulfilled and onRejected are optional arguments:
2.2.7 then must return a promise

then的两个参数是可选参数,then只能每次生成一个新的promise。就是then的时候不能返回this,如果返回this,比如第一个return失败状态,而第二个失败态里面返回了一个成功状态,这样状态发生变化,但是我们规定状态不可逆。

然后我们根据规范改造一下then方法,别忘记出错要走reject方法

then(onFulfilled?:any,onRejected?:any){
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 每次调用then都产生一个全新的Promise
    let promise2 = new Promise((resolve?:any,reject?:any)=>{
        if(this.status == STATUS.fulfilled){
            try{
                let x = onFulfilled(this.value);
                resolve(x);
            }catch(e){
                reject(e);
            }
        }
        if(this.status == STATUS.rejected){
            try{
                let x = onRejected(this.reason);
                resolve(x);
            }catch(e){
               reject(e) 
            }
        }
        if(this.status == STATUS.pending){
            this.onFulfilledCallbacks.push(()=>{
                // 切片编程 可以做额外的逻辑
                try{
                    let x = onFulfilled(this.value);
                    resolve(x);
                }catch(e){
                    reject(e);
                }
            });
            this.onRejectedCallbacks.push(()=>{
                try{
                    let x = onRejected(this.reason);
                    resolve(x);
                }catch(e){
                    reject(e);
                }
            });
        }

    })
    return promise2;
}

// 有另一种模式
const p={}
p.promise = new Promise((resolve?:any,reject?:any)=>{
    p.resolve = resolve;
    p.reject = reject
})
p.resolve(x);

第三步 当传参变化时

继续来个栗子

let promise = new Promise((resolve,rejected)=>{
        resolve('ok')
}).then(data=>{
    return new Promise((resolve,rejected)=>{
    setTimeout(()=>{
        resolve('ok');
    },1000)
})
promise.then((data)=>{
    console.log('ok')
},(err)=>{
    console.log('err')
})

我们发现之前写的方法又报错了,因为返回值x的类型变了,变成了一个Promise。所以我们要对x的类型做一个判断。具体方法还是看文档。

2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x)
[2.2.4] onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

解析:文档我们要走一个叫做Resolve的方法他有两个参数promise2x,通过它来判断x的属性是否是promise,并且对它进一步的处理,所以我们通过Resolve方法来替换掉以前的resolve(x)。并且onFulfilledonRejected必须不能调用在当前执行上下文栈中,就是说我们要用微任务来处理下这两个方法,我们这里不讨论微任务,但是我们可以用setTimeout来代替一下。

then(onFulfilled?: any, onRejected?: any) {
        // 每次调用then都产生一个全新的Promise
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
        let promise2 = new Promise((resolve?: any, reject?: any) => {
            if (this.status == STATUS.fulfilled) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // 把以前的注释掉
                        // resolve(x);
                        // 也可以用
                        // promise2.resolve = resolve;
                        // promise2.reject=reject;
                        // resolvePromise(promise2,x);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            }
            if (this.status == STATUS.rejected) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status == STATUS.pending) {
                this.onFulfilledCallbacks.push(() => {
                    // 切片编程 可以做额外的逻辑
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
            }

        })
        return promise2;
    }

第四步 判断x的类型并处理

1. 当返回是自己时

let promise = new Promise((resolve,rejected)=>{
        resolve('ok')
}).then(data=>{
    // 我等待我自己,但是我没有任何动作。这样做无意义,状态永远是pending态不会执行下面的方法
    return promise 
})
promise.then((data)=>{
    console.log('ok')
},(err)=>{
    console.log('err')
})

2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.

解析:如果x等于promise2就抛出一个TypeError错误

// 核心 解析x 来决定promise2走向成功还是失败
function resolvePromise(promise2,x,resolve,reject){
    // 判断x来决定promise2的关系 来判断有可能x是别人的promise 会有可能出问题
    if(x==promise2){
        return reject(new TypeError('出错了'))
    }
}

2.当值不是promise时,我们就是按照之前的返回resolve(x)

2.3.3 Otherwise, if x is an object or function,
2.3.4If x is not an object or function, fulfill promise with x

解析:如果x不是对象或者函数,直接让x作为promise的成功值

// 核心 解析x 来决定promise2走向成功还是失败
function resolveePromise(promise2,x,resolve,reject){
    // 判断x来决定promise2的关系 来判断有可能x是别人的promise 会有可能出问题
    if(x==promise2){
        return reject(new TypeError('出错了'))
    }
    // 只有对象或者函数是promise
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {

    } else {
        // 如果不是,肯定是个普通值(非promise的值)
        resolve(x)
    }
}

3.当值为promise时

再详细读下2.3.3里面的内容。

解析:取X上的then方法,并且x为函数的话,就用x作为thenthis去执行。而且第一个参数为成功的promiseresolvePromise叫做y,第二个参数为失败的promiserejectPromise叫做r,并且当y还为resolvePromise时继续调用Resolve方法,直到它为一个普通值为止。

// 核心 解析x 来决定promise2走向成功还是失败
function resolvePromise(promise2, x, resolve, reject) {
    // 判断x来决定promise2的关系 来判断有可能x是别人的promise 会有可能出问题
    if (x == promise2) {
        return reject(new TypeError('出错了'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let called = false; // 表示没调用过成功和失败 保证别人的pormise不能即走成功又走失败
        try {
            let then = x.then; // 取x上的then方法
            if (typeof then === 'function') {
                then.call(x, y => { //x.then 如果这样写 有可能会再去取值
                    // y 可能还是一个promise 递归解析 y 直到它是一个普通值为止
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, r => {
                    if (called) return;
                    called = true;
                    reject(r);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            // 如果取then方法失败,说明这不是一个promise 直接走失败的方法。
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        // 如果不是,肯定是个普通值
        resolve(x);
    }
}

完成

Promise有个规范测试,叫做 promises-aplus-tests 它是个全局命令,可以测试我们写的功能, 大家可以自己动手写完之后测试一下。

这样我们的初步目标达成了!完整代码:

const enum STATUS {
    pending = 'PENDING',
    fulfilled = 'FULFILLED',
    rejected = 'REJECTED'
}
// 核心 解析x 来决定promise2走向成功还是失败

function resolvePromise(promise2: Promise, x: any, resolve: any, reject: any) {
    // 判断x来决定promise2的关系 来判断有可能x是别人的promise 会有可能出问题
    if (x == promise2) {
        return reject(new TypeError('出错了'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let called = false; // 表示没调用过成功和失败 保证别人的pormise不能即走成功又走失败
        try {
            let then = x.then; // 取x上的then方法
            if (typeof then === 'function') {
                then.call(x, (y: any) => { //x.then 如果这样写 有可能会再去取值
                    // y 可能还是一个promise 递归解析 y 直到它是一个普通值为止
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, (r: any) => {
                    if (called) return;
                    called = true;
                    reject(r);
                })
            } else {
                resolve(x)
            }
        } catch (e) {
            // 如果取then方法失败,说明这不是一个promise 直接走失败的方法。
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        // 如果不是,肯定是个普通值
        resolve(x);
    }
}
class Promise {
    static deferred: Function
    status: STATUS
    value: any
    reason: any
    onFulfilledCallbacks: Function[]
    onRejectedCallbacks: Function[]
    constructor(executor: (resolve: (value?: any) => void, reject: (reason?: any) => void) => void) {
        this.status = STATUS.pending;
        this.value = undefined;
        this.reason = undefined;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = []
        const resolve = (value?: any) => {
            if (this.status === STATUS.pending) {
                this.status = STATUS.fulfilled;
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        }
        const reject = (reason?: any) => {
            if (this.status === STATUS.pending) {
                this.status = STATUS.rejected;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }
    then(onFulfilled?: any, onRejected?: any) {
        // 每次调用then都产生一个全新的Promise
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (val: any) => val;
        onRejected = typeof onRejected === 'function' ? onRejected : (err: any) => { throw err };
        let promise2 = new Promise((resolve, reject) => {
            if (this.status == STATUS.fulfilled) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // resolve(x);
                        // 也可以用
                        // promise2.resolve = resolve;
                        // promise2.reject=reject;
                        // resolvePromise(promise2,x);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0)
            }
            if (this.status == STATUS.rejected) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status == STATUS.pending) {
                this.onFulfilledCallbacks.push(() => {
                    // 切片编程 可以做额外的逻辑
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0)
                });
            }

        })
        return promise2;
    }
}
Promise.deferred = function () {
    let dfd = {} as any;
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
export default Promise;