【JavaScript】Promise

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实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容