Promise原理解析与实现

81 阅读7分钟

一、Promise 类

Promise 的构造函数接收一个函数参数(也就是需要执行异步任务的函数),该函数将在传入以后立即调用,并传入 Promise 对象下的两个方法 resolvereject

二、三种状态的实现

通过打印Promise对象,可以看到每一个 Promise 对象都存在以下三种状态:

  • pending : 进行中,Promise 对象的初始状态
  • fulfilled : 已成功
  • rejected : 已失败

每一个 Promise 对象只能由 pending 状态变成 fulfilledrejected,且状态发生变化以后就能再改变了

一个 Promise 对象状态的变化并不由 Promise 对象本身来决定,而应该是由我们传入的异步任务完成情况来决定的,Promise 提供了两个用来改变状态的方法

let p = new MyPromise((resolve, reject) => {
    resolve("success");
    // reject("err");
})
console.log(p);
  // [[Prototype]]: Promise
  // [[PromiseState]]: "fulfilled"
  // [[PromiseResult]]: "success"
class MyPromise {
    // 接收一个处理函数,并执行,执行时传入resolve函数和reject函数
    constructor(handle) {
        // 定义初始状态
        this['[[PromiseState]]'] = "pending"
        this['[[PromiseResult]]'] = undefined;
        // 注意需要绑定函数内的this指向
        handle(this.#resolve.bind(this), this.#reject.bind(this));
    }
    // 调用resolve或reject时传入参数会赋值给PromiseResult,并改变状态
    #resolve(val) {
        this['[[PromiseState]]'] = 'fulfilled';
        this['[[PromiseResult]]'] = val;
    }
    #reject(err) {
        this['[[PromiseState]]'] = 'rejected';
        this['[[PromiseResult]]'] = err;
    }
}

三、promise.then方法

1.简单实现

thenPromise 对象提供的一个方法,它接收两个函数作为参数,函数回调不能立刻执行,应该在调用resolvereject的时候执行,可以把两个函数先保存到类属性中,再在resolvereject中执行。

let p = new MyPromise((resolve, reject) => {
    // 这里先把回调放入定时器内,不然会有问题,问题是代码执行顺序问题,第3步解决
    setTimeout(() => {
        resolve("success");
        // reject("err");
    },1000)
})
p.then(res=>{
    console.log(res);
},err=>{
    console.log(err);
})
// 避免重复代码,以下代码均为在上面基础上新增
class MyPromise {
    constructor(handle) {
        this.resolveFn = undefined;
        this.rejectFn = undefined;
    }
    #resolve(val) {
        this.resolveFn && this.resolveFn(val);
    }
    #reject(err) {
        this.rejectFn && this.rejectFn(err);
    }
    then(onResolved, onRejected) {
        // 保存到类属性中
        this.resolveFn = onResolved;
        this.rejectFn = onRejected;
    }
}

2.多个then

很显然回调存储在单一变量上的方法,无法解决多个then的问题,会存在后面的回调覆盖前面回调的问题,我们可以在 Promise 中维护两个任务队列,把成功和失败的回调分别注册到两个任务队列中,在resolvereject中遍历执行队列中的任务。

let p = new MyPromise((resolve, reject) => {
    // 顺序问题第3步解决 
    setTimeout(() => {
        resolve("success");
        // reject("err");
    },1000)
})
p.then(res=>{
    console.log("1111",res);
})
p.then(res=>{
    console.log("2222",res);
})
// 避免重复代码,以下代码均为在上面基础上新增或修改
class MyPromise {
    constructor(handle) {
        // this.resolveFn = undefined;
        // this.rejectFn = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];
    }
    #resolve(val) {
        // this.resolveFn && this.resolveFn(val);
        const run = () => {
            let cb;
            // [fn1,fn2....]
            while (cb = this.resolveQueue.shift()) {
                cb && cb(val);
            }
        }
        run();
    }
    #reject(err) {
        // this.rejectFn && this.rejectFn(err);
        const run = () => {
            let cb;
            // [fn1,fn2....]
            while (cb = this.rejectQueue.shift()) {
                cb && cb(err);
            }
        }
        run();
    }
    then(onResolved, onRejected) {
        // this.resolveFn = onResolved;
        // this.rejectFn = onRejected;
        this.resolveQueue.push(onResolved);
        this.rejectQueue.push(onRejected);
    }
}

3.执行顺序问题

把定时器去掉后发现,不会执行打印,因为代码变成同步了,会先调用resolve,循环执行队列中的函数,这时队列中是空的,之后才会调用then,把回调函数加入队列中。为了解决这一问题,我们需要把resolvereject中循环执行队列这一操作放入微任务中延迟执行。

let p = new MyPromise((resolve, reject) => {
    resolve("success");
})
p.then(res=>{
    console.log(res);
})
// 避免重复代码,以下代码均为在上面基础上新增或修改
class MyPromise {
    #resolve(val) {
        // run();
        const observer = new MutationObserver(run);
        observer.observe(document.body, {
            attributes: true
        });
        document.body.setAttribute("update", "监听节点更新的微任务");
    }
    #reject(err) {
        // run();
        const observer = new MutationObserver(run);
        observer.observe(document.body, {
            attributes: true
        });
        document.body.setAttribute("update", "监听节点更新的微任务");
    }
}

4.链式操作以及返还值的处理

then 方法在执行最后必须返回一个新的 Promise 对象,支持链式操作,如果前一个then没有返回值,下一个then的参数值为undefined,如果返回一个值,将返回的值作为参数值,并对返回值进行Promise包装。如果返回一个Promise,将Promise的值作为参数值。
首先得返回 新Promise对象,并拿到上一个then的执行结果,把结果传入该对象的 resolve 或者 reject 方法中,但是同样不能立即执行,要等到原Promise对象的resolve执行,所以我们需要对原 onResolvedonRejected 进行包装,把它们和新 Promise 对象的 resolvereject 方法分别放置到新的函数中,并把这个新的函数添加到原有任务队列中调用。
简而言之:把新返回的 Promise 对象的 resolverejectthen 中执行的 onResolvedonRejected 添加到一个任务队列中执行,这样才能使用原有的 then 执行完成以后才执行新的 Promise 中的 then

let p = new MyPromise((resolve, reject) => {
    resolve("success");
})
p.then(res=>{
    console.log(1,res)
    // return "value"
    return new MyPromise(resolve=>{
        resolve("返还的值");
    })
}).then(res=>{
    console.log(2,res);
})
class MyPromise {
    then(onResolved, onRejected) {
        return new MyPromise((resolve, reject) => {
            // 不能立即执行
            // let val = onResolved && onResolved();
            let resolveFn = function (val) {
                let reslut = onResolved && onResolved(val);
                // 返还的MyPromise对象
                if (reslut instanceof MyPromise) {
                    // reslut.then(res=>{
                    //     resolve(res);
                    // })
                    reslut.then(resolve);
                } else {
                    resolve(reslut);
                }
            }
            this.resolveQueue.push(resolveFn);

            let rejectFn = function (err) {
                onRejected && onRejected(err);
                reject(err);
            }
            this.rejectQueue.push(rejectFn);
        })
        // this.resolveQueue.push(onResolved);
        // this.rejectQueue.push(onRejected);
    }
}

四、周边方法

promise.catch 方法

基于上述实现的then方法,其实就是执行原Promise的错误捕获onRejected方法

let p = new MyPromise((resolve, reject) => {
    reject("err");
})
p.then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})
class MyPromise {
    catch(fn) {
        return this.then(undefined, fn);
    }
}

promise.finally 方法

直接用then方法加入任务队列末尾

let p = new MyPromise((resolve, reject) => {
    reject("err");
})
p.then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
}).finally(() => {
    console.log("完成");
})
class MyPromise {
    finally(cb){
        this.then(cb, cb);
    }
}

Promise.resolve/Promise.reject 方法

就是返回Promise对象

let p =  MyPromise.resolve("success");
    console.log(p);
class MyPromise {
    static resolve(val) {
        return new MyPromise(resolve => {
            resolve(val);
        })
    }
    static reject(err) {
        return new MyPromise((resolve, reject) => {
            reject(err);
        })
    }
}

Promise.race 方法

返回执行最快的结果,有结果就直接返回

let p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve("success1");
        // reject("err1");
    }, 2000);
})
let p2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        // resolve("success2");
        reject("err2");
    }, 1000);
})
MyPromise.race([p1,p2]).then(res=>{
    console.log(res);
},err=>{
    console.log(err);
});
class MyPromise {
    static race(lists) {
        let isExe = false;
        return new MyPromise((resolve, reject) => {
            lists.forEach(item => {
                item.then(res => {
                    if (!isExe) {
                        resolve(res);
                        isExe = true;
                    }
                }, err => {
                    if (!isExe) {
                        reject(err);
                        isExe = true;
                    }

                })
            })
        })
    }
}

Promise.allSettled 方法

不管有没有报错,把所有的Promise实例的数据都返回回来,放入到一个对象中。如果是resolve的数据则status值为fulfilled,相反则为rejected

// p1、p2和上面一样
MyPromise.allSettled([p1,p2]).then(res=>{
    console.log(res);
})
class MyPromise {
    static allSettled(lists){
        let resArr = new Array(lists.length);
        let num = 0;
        return new MyPromise(reslove=>{
            lists.forEach((item,key)=>{
                let obj = {};
                item.then(res=>{
                    obj['status'] = "fulfilled";
                    obj['value'] = res;
                    // 保持数据返回顺序
                    resArr[key] = obj;
                    num++;
                    // 全部执行完,再返回
                    if(num>=lists.length){
                        reslove(resArr);
                    }
                },err=>{
                    obj['status'] = "rejected";
                    obj['reson'] = err;
                    resArr[key] = obj;
                    num++;
                    if(num>=lists.length){
                        reslove(resArr);
                    }
                })
            })
            
        })
    }
}

Promise.all 方法

如果有一个Promise对象报错了,则all()无法执行,会报错你的错误,无法获得其他成功的数据。

// p1、p2和上面一样
MyPromise.all([p1,p2]).then(res=>{
    console.log(res);
})
class MyPromise {
    static all(lists) {
        let resArr = new Array(lists.length);
        let num = 0;
        return new MyPromise((reslove) => {
            lists.forEach((item, key) => {
                item.then((res) => {
                    resArr[key] = res;
                    num++;
                    if (num === lists.length) {
                        reslove(resArr);
                    }
                }, (err) => {
                    // 这里num不加加,就不会返回成功的数组
                    reslove(err);
                });
            });
        });
    }
}

五、完整代码

完整代码

export default class MyPromise {
    constructor(handle) {
        this["[[PromiseState]]"] = "pending";
        this["[[PromiseResult]]"] = undefined;
        // this.resolveFn = undefined;
        // this.rejectFn = undefined;
        this.resolveQueue = [];
        this.rejectQueue = [];

        handle(this.#resolve.bind(this), this.#reject.bind(this));
    }
    #resolve(val) {
        this["[[PromiseState]]"] = "fulfilled";
        this["[[PromiseResult]]"] = val;
        // this.resolveFn && this.resolveFn(val);
        const run = () => {
            let cb;
            // [fn1,fn2....]
            while ((cb = this.resolveQueue.shift())) {
                cb && cb(val);
            }
        };
        // run();
        // setTimeout(run);
        const observer = new MutationObserver(run);
        observer.observe(document.body, {
            attributes: true,
        });
        document.body.setAttribute("update", "监听节点更新的微任务");
    }
    #reject(err) {
        this["[[PromiseState]]"] = "rejected";
        this["[[PromiseResult]]"] = err;
        // this.rejectFn && this.rejectFn(err);
        const run = () => {
            let cb;
            // [fn1,fn2....]
            while ((cb = this.rejectQueue.shift())) {
                cb && cb(err);
            }
        };
        // run();
        // setTimeout(run);
        const observer = new MutationObserver(run);
        observer.observe(document.body, {
            attributes: true,
        });
        document.body.setAttribute("update", "监听节点更新的微任务");
    }

    then(onResolved, onRejected) {
        // this.resolveFn = onResolved;
        // this.rejectFn = onRejected;
        return new MyPromise((resolve, reject) => {
            // let val = onResolved && onResolved();
            let resolveFn = function (val) {
                let reslut = onResolved && onResolved(val);
                // 返还的MyPromise对象
                if (reslut instanceof MyPromise) {
                    // reslut.then(res=>{
                    //     resolve(res);
                    // })
                    reslut.then(resolve);
                } else {
                    resolve(reslut);
                }
            };
            this.resolveQueue.push(resolveFn);

            let rejectFn = function (err) {
                onRejected && onRejected(err);
                reject(err);
            };
            this.rejectQueue.push(rejectFn);
        });
        // this.resolveQueue.push(onResolved);
        // this.rejectQueue.push(onRejected);
    }
    catch(fn) {
        return this.then(undefined, fn);
    }
    finally(cb) {
        this.then(cb, cb);
    }
    static resolve(val) {
        return new MyPromise((resolve) => {
            resolve(val);
        });
    }
    static reject(err) {
        return new MyPromise((resolve, reject) => {
            reject(err);
        });
    }
    static race(lists) {
        let isExe = false;
        return new MyPromise((resolve, reject) => {
            lists.forEach((item) => {
                item.then(
                    (res) => {
                        if (!isExe) {
                            resolve(res);
                            isExe = true;
                        }
                    },
                    (err) => {
                        if (!isExe) {
                            reject(err);
                            isExe = true;
                        }
                    }
                );
            });
        });
    }
    static allSettled(lists) {
        let resArr = new Array(lists.length);
        let num = 0;
        return new MyPromise((reslove) => {
            lists.forEach((item, key) => {
                let obj = {};
                item.then(
                    (res) => {
                        obj["status"] = "fulfilled";
                        obj["value"] = res;
                        resArr[key] = obj;
                        num++;
                        if (num >= lists.length) {
                            reslove(resArr);
                        }
                    },
                    (err) => {
                        obj["status"] = "rejected";
                        obj["reason"] = err;
                        resArr[key] = obj;
                        num++;
                        if (num >= lists.length) {
                            reslove(resArr);
                        }
                    }
                );
            });
        });
    }
    static all(lists) {
        let resArr = new Array(lists.length);
        let num = 0;
        return new MyPromise((reslove) => {
            lists.forEach((item, key) => {
                item.then(
                    (res) => {
                        resArr[key] = res;
                        num++;
                        if (num === lists.length) {
                            reslove(resArr);
                        }
                    },
                    (err) => {
                        reslove(err);
                    }
                );
            });
        });
    }
}