手把手,带你实现一个符合A+规范的Promise

203 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

一步步实现一个Promise

1. promise应该是一个构造函数或者class

    // 使用 `es6` class 语法
    class NewPromise {
        constructor() {
            
        }
    }
    const promise =  new NewPromise(); 

2. 定义三种状态。

    const PENDIND = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';

3. 初始化Promise的状态

    class NewPromise {
        constructor() {
            // promise的状态,初始为pending
            this.status = PENDIND;
            // fulfilled的值
            this.value = null;
            // rejected的值
            this.reason = null;
        }
    }

4. 实现 resolve 和 reject 方法

4.1 这两个方法要更改status,从 pending 变成 fulfilled / rejected

4.2 入参分别是 value / reason

4.3 状态变更时,需要判断当前状态是不是pending
    const PENDIND = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';


    class NewPromise {
        constructor() {
            // promise的状态,初始为pending
            this.status = PENDIND;
            // fulfilled的值
            this.value = null;
            // rejected的值
            this.reason = null;
        }

        resolve(value) {
            if(this.status === PENDIND) {
                this.value = value;
                this.status = FULFILLED;
            }
            
        }

        reject(reason) {
            if(this.status === PENDIND) {
                this.reason = reason;
                this.status = REJECTED;
            }
        }
    }

5. 对于实例化promise时的入参处理

5.1 入参是一个函数,接受resolve reject两个参数

5.2 初始化promise的时候,就要执行这个函数,并且有任何的报错都要通过reject抛出去
    class NewPromise {
        constructor(fn) {
            // promise的状态,初始为pending
            this.status = PENDIND;
            // fulfilled的值
            this.value = null;
            // rejected的值
            this.reason = null;

            // 初始化时即同步执行传入的函数
            try {
                fn(this.resolve.bind(this), this.reject.bind(this));
            } catch (error) {
                this.reject(error);
            }
        }
    }

6. then 方法

6.1 then 接受两个参数, onFulfilled 和 onRejected
    then(onFulfilled, onRejected) {
        
    }
6.2 检查并处理参数,如果参数不是函数,就忽略
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.$isFunction(onFulfilled) ? onFulfilled : (value) => value;
        const realOnRejected = this.$isFunction(onRejected) ? onRejected : (value) => value;
    }

    $isFunction(param) {
        return typeof param == 'function'
    }
6.3 then 的返回时一个整体的promise
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.$isFunction(onFulfilled) ? onFulfilled : (value) => value;
        const realOnRejected = this.$isFunction(onRejected) ? onRejected : (value) => value;

        const promise2 = new NewPromise((resolve, reject) => {});

        return promise2;
    }
6.4 根据当前promise的状态,调用不同的函数
    then(onFulfilled, onRejected) {
        const realOnFulfilled = this.$isFunction(onFulfilled) ? onFulfilled : (value) => value;
        const realOnRejected = this.$isFunction(onRejected) ? onRejected : (value) => value;

        const promise2 = new NewPromise((resolve, reject) => {
            switch (this.status) {
                case FULFILLED: {
                    realOnFulfilled();
                    break;
                }

                case REJECTED: {
                    realOnRejected();
                    break;
                }
            
                default:
                    break;
            }
        });

        return promise2;
    }
6.5 如果promise的状态不是同步改变的,应该怎么去判断状态且正确执行对应的回调

6.5.1 首先拿到所有的callback;调用then的时候,如果promise状态还是pending,就把callback存入数组
    switch (this.status) {
        case FULFILLED: {
            realOnFulfilled();
            break;
        }

        case REJECTED: {
            realOnRejected();
            break;
        }

        case PENDIND : {
            this.FULFILLED_CALLBACK_LIST.push(realOnFulfilled);
            this.REJECTED_CALLBACK_LIST.push(realOnRejected);
            break;
        }
    
        default:
            break;
    }
6.5.2 使用es6的gettersetter,监听status变化,执行相应函数
    get status() {
        return this._status;
    }

    set status(newStatus) {
        this._status = newStatus;

        switch (newStatus) {
            case FULFILLED: {
                this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                    callback(this.value);
                });
                break;
            }
            case REJECTED: {
                this.REJECTED_CALLBACK_LIST.forEach(callback => {
                    callback(this.reason);
                });
                break;
            }
        }
    }

7. then的返回值

7.1 如果onFulfilled 或者 onRejected 抛出一个异常e,那么新的promise必须reject e;
    // 封装一下方法 realOnFulfilled realOnRejected
    const fulfilledMicrotask = () => {
        try {
            realOnFulfilled(this.value);
        } catch (e) {
            reject(e);
        }
    }

    const rejectedMicrotask = () => {
        try {
            realOnRejected(this.reason);
        } catch (e) {
            reject(e)
        }
    }


    switch (this.status) {
        case FULFILLED: {
            // realOnFulfilled();
            fulfilledMicrotask()
            break;
        }

        case REJECTED: {
            // realOnRejected();
            rejectedMicrotask();
            break;
        }

        case PENDIND : {
            this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
            this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
            break;
        }
    
        default:
            break;
    }
7.2 返回值应该是一个新的promise
    then(onFulfilled, onRejected) {
        // ....

        return promise2;
    }
7.3 如果 onFulfilled 或者 onRejected 返回一个值 x,运行resolvePromise方法.
    const fulfilledMicrotask = () => {
        try {
            const x = realOnFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    const rejectedMicrotask = () => {
        try {
            const x = realOnRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
        } catch (e) {
            reject(e)
        }
    }

8. resolvePromise

    resolvePromise(promise, x, resolve, reject) {
        // 防止死循环
        if(promise === x) {
            return reject(new TypeError('The promise and the return value are the same'))
        }

        if(x instanceof NewPromise) {
            queueMicrotask(() => {
                x.then(y => {
                    this.resolvePromise(promise, y, resolve, reject)
                }, reject)
            })
        } else if(typeof x === 'object' || this.$isFunction(x)) {
            if(x === null) {
                return resolve(x)
            }

            let then = null;

            try {
                then = x.then;
            } catch (e) {
                // 如果取x.then报错,需要reject出去
                return reject(e)
            }
            // 如果then是一个函数
            if(this.$isFunction(then)) {
                // 只能执行一次
                let called = false;

                try {
                    then.call(x, (y => {
                        if(called) {
                            return;
                        }

                        called = true;
                        this.resolvePromise(promise, y, resolve, reject);
                    }), (r) => {
                        if(called) {
                            return;
                        }

                        called = true;

                        reject(r);
                    })
                } catch (e) {
                    if(called) {
                        return;
                    }
                    reject(e)
                }
            } else {
                resolve(x)
            }
        } else {
            resolve(x);
        }
    }

9. onFulfilled onRejected 应该在微任务里执行

    queueMicrotask(() => { /* code */})

几个思考

CALLBACK_LIST 设计为一个数组?

    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(111);
        }, 1000)
    });

    promise.then(value => {})
    .then(value => {})
    .then(value => {})
    .then(value => {})
    .then(value => {})

上方写法无意义:then方法返回的是一个新的promise,意味着每次CALLBACK_LIST都初始化为空,结果无意义;

设计是有意义的:对同一个promise注册多个then回调

    const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(111);
        }, 1000)
    });

    promise.then(value => {});
    promise.then(value => {});
    promise.then(value => {});
    promise.then(value => {});
    promise.then(value => {});
    promise.then(value => {});

下方console的输出?

    const testPromise = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(1111);
        }, 1000);
    }).catch(e => {
        console.log('报错',e);
        console.log(testPromise);   //testPromise 的status 为 pending
    });

    setTimeout(() => {
        console.log(testPromise);   //testPromise 的status 为 fulfilled
    }, 3000);

结果: catch 方法里输出的 promise 的 status 为 pending;setTimeout 输出的 promise 的 status 为 fulfilled

解析:

1:需要关注的是 reject掉的是最初的promise,而testPromise其实是.catch返回的新的promise,并且.catch的回调函数正常执行完成了没有报错,所以是fulfilled

2:.catch 打印的过程中,其实是在执行回调函数,而只有回调函数执行完成了,才会改变promise 的 status

完整版代码

    const PENDIND = 'pending';
    const FULFILLED = 'fulfilled';
    const REJECTED = 'rejected';


    class NewPromise {
        // 成功状态的回调
        FULFILLED_CALLBACK_LIST = [];
        // 失败状态的回调
        REJECTED_CALLBACK_LIST = [];

        // 私有变量,储存真正的status
        _status = PENDIND;

        constructor(fn) {
            // promise的状态,初始为pending
            this.status = PENDIND;
            // fulfilled的值
            this.value = null;
            // rejected的值
            this.reason = null;

            // 初始化时即同步执行传入的函数
            try {
                fn(this.resolve.bind(this), this.reject.bind(this));
            } catch (error) {
                this.reject(error);
            }
        }

        get status() {
            return this._status;
        }

        set status(newStatus) {
            this._status = newStatus;

            switch (newStatus) {
                case FULFILLED: {
                    this.FULFILLED_CALLBACK_LIST.forEach(callback => {
                        callback(this.value);
                    });
                    break;
                }
                case REJECTED: {
                    this.REJECTED_CALLBACK_LIST.forEach(callback => {
                        callback(this.reason);
                    });
                    break;
                }
            }
        }

        resolve(value) {
            if(this.status === PENDIND) {
                this.value = value;
                this.status = FULFILLED;
            }
            
        }

        reject(reason) {
            if(this.status === PENDIND) {
                this.reason = reason;
                this.status = REJECTED;
            }
        }

        then(onFulfilled, onRejected) {
            const realOnFulfilled = this.$isFunction(onFulfilled) ? onFulfilled : (value) => value;
            const realOnRejected = this.$isFunction(onRejected) ? onRejected : (value) => value;

            const promise2 = new NewPromise((resolve, reject) => {

                const fulfilledMicrotask = () => {
                    queueMicrotask(() => {
                        try {
                            const x = realOnFulfilled(this.value);
                            this.resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    });
                }

                const rejectedMicrotask = () => {
                    queueMicrotask(() => {
                        try {
                            const x = realOnRejected(this.reason);
                            this.resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    });
                }


                switch (this.status) {
                    case FULFILLED: {
                        // realOnFulfilled();
                        fulfilledMicrotask()
                        break;
                    }

                    case REJECTED: {
                        // realOnRejected();
                        rejectedMicrotask();
                        break;
                    }

                    case PENDIND : {
                        this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask);
                        this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask);
                        break;
                    }
                
                    default:
                        break;
                }
            });

            return promise2;
        }

        catch(onRejected) {
            return this.then(null, onRejected);
        }

        resolvePromise(promise, x, resolve, reject) {
            // 防止死循环
            if(promise === x) {
                return reject(new TypeError('The promise and the return value are the same'))
            }

            if(x instanceof NewPromise) {
                queueMicrotask(() => {
                    x.then(y => {
                        this.resolvePromise(promise, y, resolve, reject)
                    }, reject)
                })
            } else if(typeof x === 'object' || this.$isFunction(x)) {
                if(x === null) {
                    return resolve(x)
                }

                let then = null;

                try {
                    then = x.then;
                } catch (e) {
                    // 如果取x.then报错,需要reject出去
                    return reject(e)
                }
                // 如果then是一个函数
                if(this.$isFunction(then)) {
                    // 只能执行一次
                    let called = false;

                    try {
                        then.call(x, (y => {
                            if(called) {
                                return;
                            }

                            called = true;
                            this.resolvePromise(promise, y, resolve, reject);
                        }), (r) => {
                            if(called) {
                                return;
                            }

                            called = true;

                            reject(r);
                        })
                    } catch (e) {
                        if(called) {
                            return;
                        }
                        reject(e)
                    }
                } else {
                    resolve(x)
                }
            } else {
                resolve(x);
            }
        }

        $isFunction(param) {
            return typeof param == 'function'
        }

        static resolve(value) {
            if(value instanceof NewPromise) {
                return value;
            }

            return new NewPromise(resolve => resolve(value));
        }

        static reject(reason) {
            return new NewPromise((resolve, reject) => {
                reject(reason);
            });
        }
    }

注意:.race .all 暂且实现,等待更新;此版本和标准的Promise,在宏任务、微任务的执行顺序上会有差异,不可用于生产。同时,对于微任务的执行,此版本直接使用了 queueMicrotask api,兼容性上会有问题;