美文网首页
【前端Tip】Promise

【前端Tip】Promise

作者: 沉江小鱼 | 来源:发表于2023-10-29 19:04 被阅读0次

    1. 简介

    Promise是用来进行异步编程的。在Promise出现之前,传统的异步编程靠的是监听事件和回调函数,比如:

    const xhr = new XMLHttpRequest();
    xhr.addEventListener("load", function(data) {
        console.log(data);
    });
    xhr.open("GET", "http://www.example.com/");
    xhr.send();
    

    这种方式很可能会产生回调地狱,如果我们想在第一个请求成功并且返回数据后,再去发送第二个请求,那只能在第一个请求成功的回调函数中发送。

    以此类推,如果还想再发送下一个请求,就只能在上一个请求成功的回调函数中发送,比如:

    const xhr = new XMLHttpRequest();
    xhr.addEventListener("load", function(data) {
        console.log(data);
        const xhr1 = new XMLHttpRequest();
        xhr1.open("GET", "http://www.example.com/");
        xhr1.send();
        xhr1.addEventListener("load", function(data1) {
            console.log(data1)
            //... 发送第三个请求
        });
    });
    xhr.open("GET", "http://www.example.com/");
    xhr.send();
    

    Promise可以有效的帮助我们解决回调地狱问题,可以把Promise理解为一个容器,并且是一个有状态的对象,包括以下3种状态:

    • Pending(初始状态)
    • Fullfiled
    • Rejected

    Pending是初始状态,它可以由Pending变成Fullfiled或者Rejected,但是无论变成哪种状态都是不可逆的。

    2. Promise的基本用法

    2.1 创建Promise对象

    // 创建一个promise对象
    new Promise(function(resolve, reject) {
        ... 
        if(/*异步操作成功*/){
            resolve(data);
        }else {
            reject(err)
        }
    )
    

    Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolve和reject,他们也是函数:

    • resolve函数会修改状态:Pending -> Fullfiled,成功时调用,并将异步操作的结果作为参数传递出去;
    • reject函数会修改状态:Pending -> Rejected,失败时调用,并将异步操作报出的错误作为参数传递出去;

    需要注意一个细节:我们传进去的这个函数在创建Promise对象时就会执行。所以,有时我们会将Promise包在一个函数中,在需要的时候才去运行这个函数,比如:

    function runAsync() {
      return new Promise((resolve, reject) => {
        resolve(123);
      })
    }
    
    runAsync()
    

    我们常见的then、catch、finally方法是添加在Promise构造函数的原型对象上的。


    截屏2023-10-30 16.44.55.png

    2.2 Promise.prototype.then

    当Promise实例生成之后,可以使用then方法分别指定Fullfiled状态和Rejected状态的回调函数:

    runAsync().then(function(value)  {
        // success
    }, function(err){
        // failure
    })
    

    then方法可以接受两个回调函数作为参数:成功回调和失败回调。其中,第二个函数是可选的。这两个函数都接收Promise对象传出的值作为参数。

    需要注意的是:then方法指定的回调函数会在触发resolve和reject函数之后执行。

    到这里我们已经了解了Promise最常用的使用方式了,在实际的开发中,经常会出现上面提到的回调地狱的情况,Promise可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作,这样就可以解决回调地狱的问题了,在下面会有介绍到。

    2.3 Promise.prototype.catch

    Promise实例的catch方法,其实和then的第二个参数一样,用来指定reject的回调。用法是这样:

    promise.then((data) => {
        console.log('resolved',data);
    }).catch((err) => {
        console.log('rejected',err);
    });
    

    效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。可以看下面的代码示例:

    p.then((data) => {
        console.log('resolved',data);
        console.log(somedata); //此处的somedata未定义
    })
    .catch((err) => {
        console.log('rejected',err);
    });
    

    在resolve的回调中,我们打印的somedata这个变量是没有定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不会往下运行了。但是在这里,会得到这样的结果:


    截屏2023-10-25 20.08.58.png

    也就是说进到catch方法里面了,可以捕获到这个错误,不会阻止代码运行。

    2.4 Promise.prototype.finally

    用于指定不管Promise对象最后状态如何,都会执行的操作。

            let promise = new Promise((resolve, reject) => {
                resolve(index)
            })
            promise.then((value)=>{
                console.log(value);
            }).catch((err)=>{
                console.log(err);
            }).finally(()=>{
                console.log("不管结果如何,都会执行这里的代码");
            })
    

    3. 特殊方法(all、race、allSettled、any)

    3.1 Promise.all

    Promise.all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。简单来说,谁跑的慢就以谁为准执行回调。

    Promise.all方法接收一个数组参数,里面的值都是Promise对象,可以看下面的例子:

    let Promise1 = new Promise(function(resolve, reject){})
    let Promise2 = new Promise(function(resolve, reject){})
    let Promise3 = new Promise(function(resolve, reject){})
    
    let p = Promise.all([Promise1, Promise2, Promise3])
    
    p.then(funciton(value){
      // 三个都成功则成功  
    }, function(){
      // 只要有失败,则失败 
    })
    

    有了all,就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据。比如一个页面有多个请求,我们可以使用Promise.all方法,同时发出多个请求,在所有的请求都返回数据之后再渲染页面。

    需要注意的是,当三个Promise都执行成功后才会回调then方法中的resolve回调函数,参数值是一个数组,包括上面三个Promise执行成功回调的数据。

    3.2 Promise.race

    谁跑的快,以谁为准执行回调。
    race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作,代码如下:

     //请求某个图片资源
        function requestImg(){
            var p = new Promise((resolve, reject) => {
                var img = new Image();
                img.onload = function(){
                    resolve(img);
                }
                img.src = '图片的路径';
            });
            return p;
        }
        //延时函数,用于给请求计时
        function timeout(){
            var p = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject('图片请求超时');
                }, 5000);
            });
            return p;
        }
        Promise.race([requestImg(), timeout()]).then((data) =>{
            console.log(data);
        }).catch((err) => {
            console.log(err);
        });
    
    

    requestImg函数会异步请求一张图片,我把地址写为"图片的路径",所以肯定是无法成功请求到的。timeout函数是一个延时5秒的异步操作。我们把这两个返回Promise对象的函数放进race,于是他俩就会赛跑,如果5秒之内图片请求成功了,那么遍进入then方法,执行正常的流程。如果5秒钟图片还未成功返回,那么timeout就跑赢了,则进入catch,报出“图片请求超时”的信息。运行结果如下:


    image.png

    4. Promise的链式操作

    我们可以看下面的一个例子,当有多层回调时是怎么使用Promise的:

            function runAsync(index) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve(`执行完${index}`)
                    }, index * 500);
                })
            }
    
            runAsync(1).then(result=>{
                console.log(result);
                return runAsync(2)
            }).then(result=>{
                console.log(result);
                return runAsync(3)
            }).then(result=>{
                console.log(result);
                return runAsync(4)
            }).then(result=>{
                console.log(result);
                return "end"
            }).then(result=>{
                console.log(result);
            })
    

    输出:

    执行完1
    执行完2
    执行完3
    执行完4
    end
    

    在链式操作中,当一个Promise结束后,可以继续返回一个新的Promise,在下一个then方法中继续接收其返回的resolve。

    当然,在then方法中,也可以直接return数据而不是Promise对象,在后面的then中也可以接收到数据,可以看上面例子中的最后一个。

    5. 手写Promise

    上面简单介绍了Promise的使用,下面我们会从最基本的方法开始实现一个Promise。

    5.1 Promise基础实现

    5.1.1 Promise的基础功能

    我们先写一个只有基础功能的例子:

    const p = new Promise((resolve, reject) => {
         resolve('fulfilled');
         reject('rejected');
    })
    
    p.then(value => {
        console.log(value);
    }, reason => {
        console.log(reason);
    })
    
    // fulfilled
    

    我们来分析一下这段代码都做了什么:

    • Promise构造函数会传入一个回调函数作为参数,回调函数又会接受两个参数:resolve和reject,它们都是函数,并且会改变Promise实例对象的状态;
    • 一旦状态改变,就不会再改变了。当状态改变之后,会调用then方法的两个回调函数中的一个,并且会相对应的接收resolve或reject函数的参数值;
    • fulfilled状态下会执行then方法的第一个回调函数,rejected状态会执行第二个回调函数;

    理解了这段代码的执行逻辑之后,我们可以分两步实现Promise的基础功能,分别是:new Promise的实现原理和then方法的实现原理。

    5.1.2 new Promise的实现原理

    <script>
    
            const status_Pending = "pending"
            const status_Fulfilled = "fulfilled"
            const status_Rejected = "rejected"
    
            class CustomPromise {
                // 初始状态
                status = status_Pending
                // 成功之后的值
                value = null
                // 失败之后的原因
                reason = null
    
                constructor(executor) {
                    // 将resolve和reject传给new Promise的回调函数
                    executor(this.resolve, this.reject)
                }
    
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                    }
                }
            }
    
        </script>
    

    这里有两个注意点:

    • resolve和reject方法只有在箭头函数的情况下,才能直接传递给executor函数作为参数,这样在外部调用resolve或reject函数的时候,this指向始终是Promise实例对象。如果改为普通函数也可以,就是需要使用bind方法来稳定resolve和reject的this指向:executor(this.resolve.bind(this), this.reject.bind(this))
    • resolve和reject只有在pending状态下,蔡需要改变状态和记录结果;

    JavaScript中,bind方法用于创建一个新的函数,该函数的this值会被指定为一个特定的对象,并且在调用时,指定的参数将作为新函数的参数列表。
    https://baijiahao.baidu.com/s?id=1761949446370869152&wfr=spider&for=pc

    5.1.3 then方法的实现原理

    class CustomPromise {
      ...省略部分代码
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                        this.value = value
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                        this.reason = reason
                    }
                }
      ...省略部分代码
    }
    

    5.1.4 基础功能的完整代码

        <script>
    
            const status_Pending = "pending"
            const status_Fulfilled = "fulfilled"
            const status_Rejected = "rejected"
    
            class CustomPromise {
                // 初始状态
                status = status_Pending
                // 成功之后的值
                value = null
                // 失败之后的原因
                reason = null
    
                constructor(executor) {
                    // 将resolve和reject传给new Promise的回调函数
                    executor(this.resolve, this.reject)
                }
    
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                        this.value = value
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                        this.reason = reason
                    }
                }
    
                then(onFulfilled, onRejected) {
                    if (this.status === status_Fulfilled) {
                        // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
                        onFulfilled(this.value)
                    } else if (this.status === status_Rejected) {
                        // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
                        onRejected(this.reason)
                    }
                }
            }
        </script>
    

    我们用一开始的例子来测试一下:

            const promise = new CustomPromise((resolve, reject) => {
                resolve("value")
            })
            promise.then(value => {
                console.log(value);
            },reason => {
    
            });
    
    // value
    

    Promise 基础功能的实现原理顺利完成。

    5.2 处理异步逻辑

    基础版本的Promise有个缺点,就是无法处理异步的情况。

    
    const p = new CustomPromise((resolve, reject) => {
        setTimeout(() => {
            resolve("fulfilled");
        });
    });
    
    p.then(value => {
        console.log(value);
    }, reason => {
        console.log(reason);
    })
    
    // 不会输出任何信息
    

    由于使用了setTimeout执行resolve函数,导致then方法执行比resolve函数要早,所以then方法在执行的时候,Promise的状态是Pending,不会执行任何回调函数。

    基于这种情况,我们需要在then方法中添加处理pending状态的逻辑:如果在then方法中判断状态是pending,那么就先将两个回调函数保存起来,然后在Promise内部的resolve或reject方法中执行。

    <script>
    
            const status_Pending = "pending"
            const status_Fulfilled = "fulfilled"
            const status_Rejected = "rejected"
    
            class CustomPromise {
                // 初始状态
                status = status_Pending
                // 成功之后的值
                value = null
                // 失败之后的原因
                reason = null
                // 保存onFulfilled回调函数
                onFulfilledCallback = null
                // 保存 onRejected 回调函数
               onRejectedCallback = null
    
                constructor(executor) {
                    // 将resolve和reject传给new Promise的回调函数
                    executor(this.resolve, this.reject)
                }
    
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                        this.value = value
                        // 执行 onFulfilled 回调函数
                        if (this.onFulfilledCallback) {
                            this.onFulfilledCallback(value)
                        }
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                        this.reason = reason
                        // 执行 onRejected 回调函数
                        if (this.onRejectedCallback) {
                            this.onRejectedCallback(reason)
                        }
                    }
                }
    
                then(onFulfilled, onRejected) {
                    if (this.status === status_Fulfilled) {
                        // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
                        onFulfilled(this.value)
                    } else if (this.status === status_Rejected) {
                        // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
                        onRejected(this.reason)
                    } else {
                        // pending 状态下保存回调函数
                        onFulfilledCallback = onFulfilled
                        onRejectedCallback = onRejected
                    }
                }
            }
    
            const promise = new CustomPromise((resolve, reject) => {
                resolve("value")
            })
            promise.then(value => {
                setTimeout(() => {
                    console.log(value);
                }, 1000);
            },reason => {
    
            });
    
        </script>
    

    这样,我们就能处理异步逻辑了。

    5.3 then方法的多次调用

    别忘了,Promise实例对象的then方法是可以多次调用的,而我们现在的代码是无法做到这一点的:

    const p = new CustomPromise((resolve, reject) => {
        setTimeout(() => {
            resolve("fulfilled");
        });
    });
    
    p.then((value) => {
        console.log("1", value);
    });
    
    p.then((value) => {
        console.log("2", value);
    });
    
    p.then((value) => {
        console.log("3", value);
    });
    
    // 3 fulfilled
    

    目前的代码只能输出 3 fulfilled。为什么只能输出 3 fulfilled 呢?关键在于源码当中 then 方法保存回调函数的方式:

    class CustomPromise {
        //... 省略部分代码
        then(onFulfilled, onRejected) {
            if (this.status === status_Fulfilled) {
                onFulfilled(this.value);
            } else if (this.status === status_Rejected) {
                onRejected(this.reason);
            } else {
                // pending 状态下保存回调函数
                this.onFulfilledCallback = onFulfilled;
                this.onRejectedCallback = onRejected;
            }
        }
    }
    

    回调函数会存在成员变量中,这样就会导致保存的是最后一个then方法的回调函数,所以这里应该使用数组来保存所有的回调函数,同时Promise内部的resolve和reject方法也需要循环调用所有的回调函数:

        <script>
    
            const status_Pending = "pending"
            const status_Fulfilled = "fulfilled"
            const status_Rejected = "rejected"
    
            class CustomPromise {
                // 初始状态
                status = status_Pending
                // 成功之后的值
                value = null
                // 失败之后的原因
                reason = null
                // 保存onFulfilled回调函数
                onFulfilledCallbacks = []
                // 保存 onRejected 回调函数
                onRejectedCallbacks = []
    
                constructor(executor) {
                    // 将resolve和reject传给new Promise的回调函数
                    executor(this.resolve, this.reject)
                }
    
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                        this.value = value
                        // 执行所有的 onFulfilled 回调函数
                        this.onFulfilledCallbacks.forEach(callback => {
                            callback(this.value)
                        });
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                        this.reason = reason
                        // 执行所有的 onRejected 回调函数
                        this.onRejectedCallbacks.forEach(callback => {
                            callback(this.reason)
                        });
                    }
                }
    
                then(onFulfilled, onRejected) {
                    if (this.status === status_Fulfilled) {
                        // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
                        onFulfilled(this.value)
                    } else if (this.status === status_Rejected) {
                        // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
                        onRejected(this.reason)
                    } else {
                        // pending 状态下保存回调函数
                        this.onFulfilledCallbacks.push(onFulfilled)
                        this.onRejectedCallbacks.push(onRejected)
                    }
                }
            }
    
            const promise = new CustomPromise((resolve, reject) => {
                resolve("value")
            })
            promise.then(value => {
                setTimeout(() => {
                    console.log(value);
                }, 1000);
            },reason => {
    
            });
    
        </script>
    

    这其实是一个「观察者模式」,then 方法的 this.onFulfilledCallbacks.push(onFulfilled) 和 this.onRejectedCallbacks.push(onRejected) 就是在添加订阅者,而 resolve 和 reject 方法就是在通知所有的订阅者。

    我们再运行一下用例,得出结果:

    1 fulfilled
    2 fulfilled
    3 fulfilled
    

    5.4 then方法的链式调用

    Promise最核心的功能就是then方法的链式调用,这也是解决回调地狱的关键。我们目前手动实现的代码是不能进行then方法的链式调用的,因为then方法没有任何返回值。

    要想实现then方法的链式调用,then方法必须返回Promise对象,并且下一个then方法的回调函数的参数会依赖上一个then方法的回调函数的返回值,这种依赖有两种情况:

    • 如果返回的是Promise对象,那么下一个then方法的回调函数会接受该实例对象的resolve或reject函数传入的值,比如:
    const p = new Promise((resolve, reject) => {
        resolve(1);
    });
    p.then((value) => {
        console.log(value);
        return new Promise((resolve, reject) => {
            resolve(2);
            // reject(2);
        });
    }).then((value) => {
        console.log("fulfilled", value);
    }, (err) => {
        console.log("rejected", err);
    });
    
    // 1
    // fulfilled 2
    
    // 如果调用的是 reject(2),那么返回的是:
    // 1
    // rejected 2
    
    • 如果返回的是 thenable 对象,会和第一种情况一样:
    const p = new Promise((resolve, reject) => {
        resolve(1);
    });
    p.then((value) => {
        console.log(value);
        return {
            then(resolve, reject) {
                resolve(2);
                // reject(2);
            }
        };
    }).then((value) => {
        console.log("fulfilled", value);
    }, (err) => {
        console.log("rejected", err);
    });
    
    // 1
    // fulfilled 2
    
    // 如果调用的是 reject(2),那么返回的是:
    // 1
    // rejected 2
    
    • 如果返回的是其他对象或者原始数据类型的值,那么下一个then方法的回调函数的参数会直接接收这个值,比如:
    const p = new Promise((resolve, reject) => {
        resolve(1);
    });
    p.then((value) => {
        console.log(value);
        return {
            value: 2,
        };
    }).then((value) => {
        console.log("fulfilled", value);
    });
    
    // 1
    // fulfilled { value: 2 }
    

    了解了then方法链式调用的基本情况后,我们手动实现一下then方法的链式调用。

    首先,让then方法返回一个Promise对象:

    then(onFulfilled, onRejected) {
           const promise2 = new CustomPromise((resolve, reject) => {
                 if (this.status === status_Fulfilled) {
                    // 把 resolve 的值传递给 fulfilled 状态的回调函数,并且调用它。
                    onFulfilled(this.value)
                } else if (this.status === status_Rejected) {
                    // 把 reject 的值传递给 rejected 状态的回调函数,并且调用它。
                    onRejected(this.reason)
                } else {
                     // pending 状态下保存回调函数
                    this.onFulfilledCallbacks.push(onFulfilled)
                    this.onRejectedCallbacks.push(onRejected)
               }
            }) 
            return promise2
    }
    

    接着,通过resolve和reject函数来改变promise2的状态,并且建立上一个then和下一个then的依赖关系:

        <script>
    
            const status_Pending = "pending"
            const status_Fulfilled = "fulfilled"
            const status_Rejected = "rejected"
    
            class CustomPromise {
                // 初始状态
                status = status_Pending
                // 成功之后的值
                value = null
                // 失败之后的原因
                reason = null
                // 保存onFulfilled回调函数
                onFulfilledCallbacks = []
                // 保存 onRejected 回调函数
                onRejectedCallbacks = []
    
                constructor(executor) {
                    // 将resolve和reject传给new Promise的回调函数
                    executor(this.resolve, this.reject)
                }
    
                // resolve
                resolve = (value) => {
                    // 改变状态为fulfilled
                    if (this.status === status_Pending) {
                        this.status = status_Fulfilled
                        this.value = value
                        // 执行所有的 onFulfilled 回调函数
                        this.onFulfilledCallbacks.forEach(callback => {
                            callback(this.value)
                        });
                    }
                }
    
                // rejected
                reject = (reason) => {
                    // 改变状态为 rejected
                    if (this.status === status_Pending) {
                        this.status = status_Rejected
                        this.reason = reason
                        // 执行所有的 onRejected 回调函数
                        this.onRejectedCallbacks.forEach(callback => {
                            callback(this.reason)
                        });
                    }
                }
    
                then(onFulfilled, onRejected) {
                    const promise2 = new CustomPromise((resolve, reject) => {
                        if (this.status === status_Fulfilled) {
                            // 获取上一个then方法的fulfilled回调函数的返回值
                            const v = onFulfilled(this.value)
                            // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                            resolvePromise(v, resolve, reject)
                        } else if (this.status === status_Rejected) {
                            // 获取上一个then方法的rejected回调函数的返回值
                            const v = onRejected(this.reason)
                            // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                            resolvePromise(v, resolve, reject)
                        } else {
                            // pending 状态下保存回调函数
                            this.onFulfilledCallbacks.push(onFulfilled)
                            this.onRejectedCallbacks.push(onRejected)
                        }
                    })
                    return promise2
                }
    
                resolvePromise(value, resolve, reject) {
                    if (typeof value === "object" || typeof value === "function") {
                        if (value === null) {
                            // 如果返回值是null,直接调用resolve函数,promise2的状态变为fulfilled
                            resolve(value)
                        }
                        try {
                            if (typeof value.then === "function") {
                                // 如果返回值是 Promise 对象或者 thenable 对象
                                // 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值
                                // 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))
                                value.then(resolve, reject)
                            } else {
                                // 如果 then 不是函数,同 null 情况一样的处理逻辑。
                                resolve(value);
                            }
                        } catch (error) {
                            // 出现异常的情况下,调用 reject 函数
                            // promise2 的状态变为 rejected,
                            // 错误信息由下一个 then 方法的第二回调函数接收
                            reject(error);
                        }
                    } else {
                        // 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。
                        resolve(value);
                    }
                }
            }
        </script>
    

    这里比较难理解的是,返回值为Promise对象或者thenable对象的处理情况:value.then(resolve, reject),这段代码写完整一点就是:

    value.then((value) => resolve(value), (err) => reject(err))
    

    这里 then 方法的回调函数中 value 参数值和 err 参数值就是 Promise 对象或者 thenable 对象内部调用 resolve 或者 reject 函数传入的参数值,再把这些值传递给 promise2 的 resolve 和 reject 函数,从而达到改变 promise2 的状态,下一个 then 方法的回调函数也会被调用并且接收到这些值。

    总得来说就是,promise2 的状态完全由返回值(Promise 对象或者 thenable 对象)来控制。就跟以下这段代码一样:

    
    const promise2 = new CustomPromise((resolve2, reject2) => {
        const value = new CustomPromise((resolve, reject) => {
            resolve(1);
        });
        value.then(
            (v) => resolve2(v),
            (err) => reject2(err)
        );
    });
    
    promise2.then((value) => {
      console.log(value);
    });
    
    // 1
    

    我们执行一下测试用例,看我们自己写的Promise是否可以正常工作:

    
    const p = new CustomPromise((resolve, reject) => {
      resolve(1);
    });
    p.then((value) => {
      console.log(value);
      return new CustomPromise((resolve) => {
        resolve(2);
      });
    }).then((value) => {
      console.log(value);
    });
    
    // 1
    // 2
    
    
    
    p.then((value) => {
      console.log(value);
      return {
        then(resolve) {
          resolve(2);
        }
      };
    }).then((value) => {
      console.log(value);
    });
    
    // 1
    // 2
    
    
    p.then((value) => {
      console.log(value);
      return 2;
      };
    }).then((value) => {
      console.log(value);
    });
    
    // 1
    // 2
    

    那如果 then 方法是返回自身的 Promise 对象该怎么办?我们来看看原生的 Promise 是怎么处理的:

    
    const p = new Promise((resolve, reject) => {
      resolve(1);
    });
    const p1 = p.then(value => {
      console.log(value);
      return p1;
    });
    
    // 1
    // Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
    

    报错,错误信息是:会发生 Promise 循环调用。
    所以,我们需要改造一下 SelfPromise 的代码,来模拟这种报错的效果:

     then(onFulfilled, onRejected) {
                    const promise2 = new CustomPromise((resolve, reject) => {
                        if (this.status === status_Fulfilled) {
                            // 获取上一个then方法的fulfilled回调函数的返回值
                            const v = onFulfilled(this.value)
                            // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                            // 将 promise2 传入进行判断
                            this.resolvePromise(promise2, v, resolve, reject)
                        } else if (this.status === status_Rejected) {
                            // 获取上一个then方法的rejected回调函数的返回值
                            const v = onRejected(this.reason)
                            // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                            // 将 promise2 传入进行判断
                            this.resolvePromise(promise2, v, resolve, reject)
                        } else {
                            // pending 状态下保存回调函数
                            this.onFulfilledCallbacks.push(onFulfilled)
                            this.onRejectedCallbacks.push(onRejected)
                        }
                    })
                    return promise2
                }
    
                resolvePromise(promise2, value, resolve, reject) {
                    // 如果 then 方法返回的是自身 Promise 对象,返回错误信息
                    if (promise2 === value) {
                        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
                    }
                    // 如果then方法返回的是自身Promise对象,返回错误信息
                    if (typeof value === "object" || typeof value === "function") {
                        if (value === null) {
                            // 如果返回值是null,直接调用resolve函数,promise2的状态变为fulfilled
                            return resolve(value)
                        }
                        try {
                            if (typeof value.then === "function") {
                                // 如果返回值是 Promise 对象或者 thenable 对象
                                // 那就只能交给它们的 then 方法来改变 promise2 的状态,以及获取相对应的状态值
                                // 以下代码等同于 value.then((value) => resolve(value), (err) => reject(err))
                                value.then(resolve, reject)
                            } else {
                                // 如果 then 不是函数,同 null 情况一样的处理逻辑。
                                resolve(value);
                            }
                        } catch (error) {
                            // 出现异常的情况下,调用 reject 函数
                            // promise2 的状态变为 rejected,
                            // 错误信息由下一个 then 方法的第二回调函数接收
                            reject(error);
                        }
                    } else {
                        // 如果返回值是其他对象或者原始数据类型值,同 null 情况一样的处理逻辑。
                        resolve(value);
                    }
                }
            }
    

    我们使用自己构建的CustomPromise尝试下之前的例子:

    const p = new CustomPromise((resolve, reject) => {
                resolve(1);
            });
            const p1 = p.then(value => {
                console.log(value);
                return p1;
            });
    
    

    发现错误:


    截屏2023-10-30 17.27.02.png

    发现错误信息不一样,这里根据提示可以知道,我们在p1定义之前就使用了它。实际情况也确实如此,我们先等then方法里面的回调函数执行完毕之后,then方法再返回Promise对象,但我们却在回调函数内先用了这个Promise对象,所以才报这个错误信息。

    那怎么解决呢?其实只需要把then方法的回调函数的同步执行改为异步执行就可以了,这里我使用setTimeout进行处理:

    then(onFulfilled, onRejected) {
                    const promise2 = new CustomPromise((resolve, reject) => {
                        if (this.status === status_Fulfilled) {
                            setTimeout(() => {
                                // 获取上一个then方法的fulfilled回调函数的返回值
                                const v = onFulfilled(this.value)
                                // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                                // 将 promise2 传入进行判断
                                this.resolvePromise(promise2, v, resolve, reject)
                            });
                        } else if (this.status === status_Rejected) {
                            setTimeout(() => {
                                // 获取上一个then方法的rejected回调函数的返回值
                                const v = onRejected(this.reason)
                                // 根据返回值,改变promise2的状态,并建立与下一个then方法的关系
                                // 将 promise2 传入进行判断
                                this.resolvePromise(promise2, v, resolve, reject)
                            });
                        } else {
                            // pending 状态下保存回调函数
                            // pending 状态下保存回调函数
                            this.onFulfilledCallbacks.push((value)=>{
                                setTimeout(() => {
                                    onFulfilled(value)   
                                })
                            })
                            this.onRejectedCallbacks.push((reason)=>{
                                setTimeout(() => {
                                    onRejected(reason)   
                                })
                            })
                        }
                    })
                    return promise2
                }
    

    测试通过:

            const p = new CustomPromise((resolve, reject) => {
                resolve('1')
            })
    
            // 返回自身 Promise 对象
            const p1 = p.then(value => {
                console.log(value);
                return p1;
            })
    
            // 接收错误信息
            p1.then(value => {
                console.log(2);
                console.log('fulfilled', value)
            }, err => {
                console.log(3);
                console.log('reject', err.message);
            })
            // 1
            // 3
            // reject Chaining cycle detected for promise #<Promise
    

    5.5 catch方法实现原理

    catch 方法是用于指定发生错误时的回调函数,它其实就是对 then 方法的调用,想想我们之前是通过 then 方法的第二个参数来接收 rejected 状态的错误:

    const p = new CustomPromise((resolve, reject) => {
        reject(1);
    })
    p.then((value) => {
        console.log("fulfilled", value);
    }, (reason) => {
        console.log("rejected", reason);
    });
    
    // rejected 1
    

    所以,catch 方法等同于 then(null, onRejected) 或 then(undefined, onRejected),因此实现原理也很明了:

    
    // ...省略部分代码
    class CustomPromise {
        // ...省略部分代码
        catch(onRejected) {
            return this.then(null, onRejected);
        }
    }
    

    5.5 finally 方法实现原理

    finally方法也是一个语法糖,它用于在Promise实例上注册一个处理函数,无论Promise是成功还是失败,该处理函数都会被调用:

    class CustomPromise {
      // ...其他代码
    
      finally(callback) {
        // 调用then方法,传入两个相同的处理函数
        return this.then(
          value => {
            // 创建一个新的Promise实例,确保异步执行callback
            return CustomPromise.resolve(callback()).then(() => value);
          },
          reason => {
            // 创建一个新的Promise实例,确保异步执行callback
            return CustomPromise.resolve(callback()).then(() => { throw reason; });
          }
        );
      }
    }
    

    我们看到上面的方法实现中依赖静态的resolve和reject方法,在下面会有介绍。

    5.6 Promise.resolve、Promise.reject静态方法

    这两个方法可以快速的创建一个已经解决或拒绝的Promise实例:

                static resolve(value) {
                    if (value instanceof CustomPromise) {
                        return value
                    }
                    return new CustomPromise((resolve, reject) => {
                        resolve(value)
                    })
                }
    
                static reject(value) {
                    return new CustomPromise((resolve, reject) => {
                        reject(value)
                    })
                }
    

    这一步中,我们实现了resolve和reject静态方法,resolve方法接收一个参数:value,用于创建一个已经解决的Promise实例。reject方法接收一个参数:reason,用于创建一个已经拒绝的Promise实例。

    相关文章

      网友评论

          本文标题:【前端Tip】Promise

          本文链接:https://www.haomeiwen.com/subject/tbucidtx.html