Promise总结

1,043 阅读7分钟

最近看了阮一峰ES6入门中Promise部分的内容,Promise一直是学习以及面试中的重点,这里主要记录Promise的原生写法,以及使用Promise的一些注意事项,关于Promise基础部分的内容都可以在红宝书以及阮一峰的博客上面找到。

构造函数

function MyPromise(executor) {

    this.PromiseState = "pending";
    this.PromiseResult = undefined;
    this.callbackList = [];

    let resolve = (value) => {
        if (this.PromiseState !== "pending") return
        this.PromiseState = "fulfilled";
        this.PromiseResult = value;
        for (let callback of this.callbackList) {
            callback.onResolved(value);
        }

    };
    let reject = (reason) => {
        if (this.PromiseState !== "pending") return
        this.PromiseState = "rejected";
        this.PromiseResult = reason;
        for (let callback of this.callbackList) {
            callback.onRejected(reason);
        }

    };

    try {
        executor(resolve, reject);
    } catch (err) {
        reject(err);
    }
}

注意点:

  1. Promise对象的状态一经改变,就不会再变了。因此在回调函数resolvereject中应该加一句代码来判断当前Promise对象的状态是否已经改变,改变了的话就不能再改变它的状态了。在下面的例子中说明,Promise状态由pending转变为fulfilled,之后执行到reject(222)时,不会再改变状态了。
let p = new Promise((resolve, reject) => {
    resolve(111);
    reject(222)
})
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: 111
console.log(p);
  1. 构造函数传入一个executor函数,这个函数有两个参数:resolvereject,函数传入就会执行。
  2. 如果在executor函数中抛出错误,也会触发reject函数。
  3. 最后一个重点是,在executor函数中,Promise的状态改变如果依赖于一个异步操作,例如setTimeout,只有在setTimeout的延时时间到了以后才会调用resolve或reject来改变Promise的状态。因此要将这两个回调函数保存起来,等将来延时时间到了以后就可以正常触发回调函数,考虑到这种情况,可以直接设置一个数组来接收这些回调函数。
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(111)
    }, 1000)
})

console.log(p);

then方法

MyPromise.prototype.then = function (onResolved, onRejected) {

    return new MyPromise((resolve, reject) => {

        let handleCallback = (callback) => {
            let res = undefined;
        // 判断then方法的回调是函数还是空值
        // 这里的逻辑可以简化为:
        // 如果回调是函数,那么res就是回调return的结果,
        // 没有返回结果此时返回的Promise实例的PromiseResult为undefined
        // 如果回调函数没得参数,那么此时then方法返回的Promise对象的
        // PromiseResult等于当前对象的PromiseResult
        // 解决穿透问题
            if (typeof callback === "function") {
                res = callback(this.PromiseResult);
            } else {
                res = this.PromiseResult;
            }
            try {
                if (res instanceof Promise) {
                    res.then((value) => {
                        resolve(value)
                    }, (err) => {
                        reject(err);
                    })
                } else {
        // 这段代码有一个作用就是在finally函数中,除了在finally函数的回调函数中
        // 抛出错误或返回状态为rejected的Promise,其它情况下finally返回的Promise对象的状态
        // 都依赖于当前Promise的状态。因此,在then方法中除了判断当前Promise对象返回的值
        // 是什么类型,还要依据当前Promise对象状态来决定finally回调函数返回的Promise的状态
                    if (this.PromiseState === "fulfilled") {
                        resolve(res);
                    }
                    if (this.PromiseState === "rejected") {
                        reject(res);
                    }

                }
            } catch (err) {
                reject(res);
            }
        }

        if (this.PromiseState === "fulfilled") {
            console.log("fulfilled")
            handleCallback(onResolved);
        }
        if (this.PromiseState === "rejected") {
            console.log("rejected")
            handleCallback(onRejected);
        }
        if (this.PromiseState === "pending") {
            this.callbackList.push({
                onResolved: () => {
                    handleCallback(onResolved);
                },
                onRejected: () => {
                    handleCallback(onRejected);
                }
            })
        }
    })
}

测试用例:

let p = new MyPromise((resolve, reject) => {
    resolve(122344)
}).then().then(console.log).then((val) => {
    // undefined
    console.log(val)
    return 89
})
// PromiseResult: 89
// PromiseState: "fulfilled"
console.log(p);

let p1 = new MyPromise((resolve, reject) => {
    resolve(122344)
}).then().then().then((val) => {
    // 122344
    console.log(val)
})
// PromiseResult: undefined
// PromiseState: "fulfilled"
console.log(p1);

注意点:

  1. then()方法接收两个回调函数作为参数,只有在Promise状态改变时才会触发这两个函数。
  2. 在执行Promise的同步代码时,可能会返回一个Promise对象,要通过then方法先把这个Promise实例的状态及返回值确定下来。

catch方法

// catch()方法可以直接用then()方法来实现
MyPromise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
}

Promise.resolve()

MyPromise.resolve = function (val) {

    return new MyPromise((resolve, reject) => {
        // Promise不能递归调用
        if (this === val) reject("Chaining cycle detected for promise #<Promise>");
        if (val instanceof MyPromise) {
            val.then((value) => {
                resolve(value)
            }, (err) => {
                reject(err)
            })
        } else {
            resolve(val);
        }
    })
}

Promise.reject()

// Promise.reject()的参数会原封不动的返回
MyPromise.reject = function (val) {
    return new MyPromise((resolve, reject) => {
        reject(val);
    })
}

Promise.finally()

// finally返回的Promise的状态除了返回rejected状态的Promise或抛出异常
// 其它情况返回的Promise的状态都跟当前Promise的状态一样
// 因此自己编写Promise时,要优先考虑当前Promise的状态
MyPromise.prototype.finally = function (callback) {
    return this.then(
        (value) => {
            console.log(value);
            // finally方法可能会返回Promise对象
            // 因此要把这个Promise实例的状态以及返回值确定下来
            // 只要出现问题,都会触发then方法中的reject函数
            // 回调函数返回一个异常,新的Promise对象的状态也和当前Promise对象一致
            MyPromise.resolve(callback()).then(null, (err) => {
                return err
            })
            // 返回和当前Promise对象一致的PromiseResult
            return value
        },
        (err) => {
            console.log(err);
            MyPromise.reject(callback()).then(null, (err) => {
                return err
            })
            return err
        }
    )
}

Promise.all()

MyPromise.all = function (promiseList) {
    let res = [];
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < promiseList.length; i++) {
            promiseList[i].then((value) => {
                res[i] = value;
                if (res.length === promiseList.length) {
                    resolve(res);
                }
            }, (err) => {
                reject(err);
            })
        }
    })
}

Promise.race()

MyPromise.race = function (promiseList) {
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < promiseList.length; i++) {
            promiseList[i].then((value) => {
                resolve(value)
            }, (err) => {
                reject(err);
            })
        }
    })
}

Promise.allSettled()

MyPromise.allSettled = function (promiseList) {
    let res = [];
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < promiseList.length; i++) {
            promiseList[i].then((value) => {
                res[i] = value;
                resolve(value)
            }, (err) => {
                res[i] = err;
                reject(err);
            })
        }
        if (res.length === promiseList.length) {
            resolve(res);
        }
    })
}

Promise.any()

MyPromise.any = function (promiseList) {
    let res = [];
    return new MyPromise((resolve, reject) => {
        for (let i = 0; i < promiseList.length; i++) {
            promiseList[i].then((value) => {
                resolve(value)
            }, (err) => {
                res[i] = err;
                if (res.length === promiseList.length) {
                    reject(res);
                }
            })
        }
    })
}

你确定你现在弄明白Promise了吗?来看个例题

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
})

在看这道题之前我们需要具备这些知识:

  • Promise中只有涉及到状态变更后才需要被执行的回调函数才算是微任务,比如thencatchfinally。在使用Promise.resolve()时,这不是一个微任务,它只是定义了一个Promise实例,并改变其状态。

现在来公布答案~~~

打印结果:0123456

这道题是在我手写Promise时在看的一篇文章里面的题,文章也做了解释。

先看一个实例:

let p = new Promise((resolve, reject) => {
    return p
})

// Uncaught (in promise) ReferenceError: Cannot access 'p' before initialization

从这里可以看出,必须要等到Promise初始化完成后,才能进行下一步,因此,在这里就需要创建一个异步任务去等待p的初始化完成。其实可以这样理解,当我们在初始化一个Promise实例时,肯定要让它初始化完成撒~

接下来解释一下这道题的打印过程:

  • 一开始有两个resolveresolve是一个同步操作,因此它们分别执行then()方法;
  • 第一个resolve打印0,并返回一个resolve状态的Promise,第二个resolve打印1;
  • 刚才我们说了,返回一个Promise时,会添加一个微任务,此时就会执行第二个resolve的第二个then方法,打印出2;
  • 现在再来看Promise.resolve(4),在then方法中返回一个Promise对象时,这是就会调用这个Promise的then方法,可以看上面then()方法中是怎么写的,then方法执行完回调函数返回一个Promise就会调用其then方法,将参数传递出去;接下来,第二个Promise也会执行它的下一个它then方法,打印出3;
  • 接下来又来到一个resolve,这时Promise.resolve(4)已经将返回结果作为参数传递给下一个then方法,打印出4;
  • 最后,第二个Promise分别打印出5和6. 关于这里在then方法中返回Promise.resolve()的理解,可以直接简化成一条规律:只要是返回一个具有状态的Promise,它都会延迟两个then执行。

再来两个例题帮助加深理解Promise

例一

Promise.resolve()
  .then(() => {
    console.log("then1");
    Promise.resolve().then(() => {
      console.log("then1-1");
    });
  })
  .then(() => {
    console.log("then2");
  });

打印结果:then1 → then1-1 → then2

在链式调用时,只有前一个then方法的回调函数执行完,下一个then方法的回调才能被加入到微任务队列中。

例二

let p = Promise.resolve();

p.then(() => {
  console.log("then1");
  Promise.resolve().then(() => {
    console.log("then1-1");
  });
}).then(() => {
  console.log("then1-2");
});

p.then(() => {
  console.log("then2");
}); 

打印结果:then1 → then2 → then1-1 → then1-2

同一个Promise的每一个链式调用的开端会依次首先进入微任务队列。

把上面的例子换个写法:

let p = Promise.resolve().then(() => {
  console.log("then1");
  Promise.resolve().then(() => {
    console.log("then1-1");
  });
}).then(() => {
  console.log("then2");
});

p.then(() => {
  console.log("then3");
});

打印结果:then1 → then1-1 → then2 → then1-3

p.then这里的p已经不再是最开始定义的那个,而是调用最后一个then生成的。

最后

这篇文章主要是自己在学习Promise以后总结下来的,文章里面的例子主要是借鉴其它作者的,下面必须要贴一下文章来源

juejin.cn/post/694531…

juejin.cn/post/686957…

下面是我对Promise的总结,写在Processon上面的,贴下网址 www.processon.com/outline/603…

山高路远,道阻且长,文章会不定时更新~学无止境,欢迎大家一起交流学习#