一文打通Promise的任督二脉

1,207 阅读7分钟

hello大家好,我是小九九的爸爸,临近过年,准备再最后卷一卷,我们这篇文章讲解的知识点是promise,这篇文章的主题将会紧紧围绕下面这幅图展开:

截屏2024-02-04 23.31.53.png

那废话不多说,直接进入正题。

什么是Promise?

它其实就是一个构造函数,通过 new 这样的构造函数来创建一个promise实例,最后使用promise相关的API,让你的代码能够在未来的可预测的某个时刻去执行。

它解决了什么问题?

它提供了一种让代码异步运行的全新解决方案。

它有哪些特点?

  • promise实例3种状态,分别是pending(进行中)、fulfilled(已完成)、rejected(已失败)。
  • 一个promise实例的状态只能改变一次,且不能回滚。
  • promise暴露了一些API,支持你监听promise状态的改变。
  • 所有的Promise的API都会返回一个新的promise实例。
  • 当我们new Promise构造函数时,构造函数里的代码是立即执行的。

如何创建Promise实例?

截屏2024-02-05 08.50.31.png

如何进行错误处理?

处理promise相关的错误时,大致分为3类,分别如下:

截屏2024-02-05 08.53.46.png

这一块大家还是要了解它的明细的,因为我们使用promise,80%的场景下都是并发处理,错误处理不恰当,就会导致意想不到的bug发生。

Promise相关的API有哪些?

这一块的API较多,我大致列举了一下思维导图,如下:

截屏2024-02-05 08.56.24.png

我们重点看一下后面几个API,分别如下:

Promise.all

参数:由promise实例组成的数组。

数组里各项的执行顺序:并发执行。

返回:如果有一个实例失败,那么就返回失败的信息;如果全部都成功了,那么就会返回一个数组,数组里包含了每个promise实例成功的信息。

运行流程:
1、如果各promise实例的状态都是resolve,那么就会先执行各自实例的 then函数,然后再执行 Promise.all.then。
2、如果promise实例中,有一个状态被reject了,那么就会触发Promise.all.catch,其余还没执行完的promise实例将继续执行,只不过无论如何都不会再触发Promise.all.catch 或者 Promise.all.then了。

我们实际code一下:

let p1 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(1);        
        }, 1000);
    });
}

let p2 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(2);        
        }, 2000);
    }).then(res => {
        console.log('p2的then实例:', res);    
    });
}

Promise.all([p1(), p2()]).then(res => {
    console.log('最终收到的响应:', res);
})

上面的代码运行以后,我们的控制台会输出下面的信息:

/***
	p2的then实例: 2
	最终收到的响应: [ 1, undefined ]
*/

为什么数组第二项是一个undefined呢?最开始我们说过,promise里所有的API都会返回一个新的Promise实例,我们给p2注册的then方法里并没有明确返回值,所以输出是undefined,符合预期。

Promise.race

参数:由promise实例组成的数组。

数组里各项的执行顺序:并发执行。

返回:返回实例里状态改变最快的实例结果。

运行流程:
1、多个promise实例,看谁的状态最先发生改变,那么 promise.race.then 或者 promsie.race.catch 就返回 最快的resolve或者reject。
2、如果有没跑完的promise实例就接着跑,只是不会再触发 promise.race.then 或者 promise.race.catch。

我们来coding一下:

let p1 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(1);        
        }, 1000);
    });
}

let p2 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(2);        
        }, 2000);
    }).then(res => {
        console.log('p2的then实例:', res);
        return res;
    });
}

Promise.race([p1(), p2()]).then(res => {
    console.log('最终收到的响应:', res);
})

运行一下代码,结果如下:

/***
	最终收到的响应: 1
	p2的then实例: 2
*/

Promise.allSettled

参数:由promise实例组成的数组。

数组里各项的执行顺序:并发执行。

返回:将各实例的结果按顺序组成一个数组并返回。

运行流程:
1、会等待所有的promise实例状态都发生变化,才会去触发 Promise.allSettled.then。 返回的是一个结果数组。
2、每一项都包含status状态(fulfilled、rejected)字段来标识promise实例是resolve还是被reject。

我们再把这个coding一下:

let p1 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(1);        
        }, 1000);
    });
}

let p2 = function(){
    return new Promise((resolve, reject) => {
        setTimeout(function(){
            resolve(2);        
        }, 2000);
    }).then(res => {
        console.log('p2的then实例:', res);
        return res;
    });
}

Promise.allSettled([p1(), p2()]).then(res => {
    console.log('最终收到的响应:', res);
})

看下控制台的输出结果:

/***
	p2的then实例: 2
    最终收到的响应: [
      { status: 'fulfilled', value: 1 },
      { status: 'fulfilled', value: 2 }
    ]
*/

Promise.any

参数:promise实例组成的数组。

各实例的执行顺序:并发执行。

返回:哪个promise的实例状态最先被置为fulfilled,就返回它。

这个就不coding了,留给大家自己去实践吧。

Promise相关应用有哪些?

下面列举了一些比较常见的应用,如下:

截屏2024-02-05 22.19.54.png

我们来一个一个的攻克。

promise实现并发请求

这个主要考察了Promise.all、Promise.allSettled 的应用。当然在实际工作中,allSettled的使用频率较高,这里就不细说了。

promise实现红绿灯交替闪烁

相信大家都遇到过这样的题目:

1s红灯,下一秒绿灯,下一秒黄灯,再下一秒红灯,如此往复...

这种重复执行的内容,采用递归一定是不行的。因为每次调用一个函数,都会为它创建执行上下文并压入栈中,栈的容量是有限的,因为这道题不存在pop场景,所以使用递归,一定会爆栈。

定时器setInterval + 全局变量就是一个很好的实现手段,但是不知道为啥,这种题目会跟promise关联上,也想不懂这么关联的意义在哪。有知道为啥的小伙伴可以在评论区里告诉我一下。

如何实现promise.all

想要实现这个内容,首先我们要想想这个API的特点以及实现的难点:

  • 返回一个promise实例
  • 数组里各项都是并发进行。
  • 返回最先失败的那个实例,如果都成功,就将各实例的结果 拼成数组 并返回。
  • 难点:如何监测promise实例发生了改变?这个点我们可以直接在各promise实例后面再添加 then方法 and catch方法来解决。

有了上述的分析,我们就可以实现all这个API:

let customAll = (instanceArr) => {
    let failReason = null;
    let result = [];
    return new Promise((fulfilled, rejected) => {
        instanceArr.forEach(item => {
            item.then(succcess => {
                result.push(succcess);
                if (result.length === instanceArr.length){
                    // 说明全部都执行成功啦,直接返回
                    return fulfilled(result);
                }
            })
            .catch(err => {
                if (failReason === null){
                    // 返回第一个失败的实例
                    failReason = err;
                    return rejected(failReason);
                }
            })
        });
    });
}

如何实现promise.allSettled

这个的实现思路跟all差不多,主要修改2个点:

  • 修改返回信息。all是直接将最后的结果(value)返回给用户,而allSettled还需要将状态(status)返回给用户。
  • catch方法里不能直接reject了,因为这个allSettled方法总是会在最外面包裹一层fulfilled并返回给用户。

有了上面的分析,不难写出这个方法的实现:

let customAllSettled = (instanceArr) => {
    let failReason = null;
    let result = [];
    return new Promise((fulfilled, rejected) => {
        instanceArr.forEach(item => {
            item.then(succcess => {
                result.push({
                    status: 'fulfilled',
                    value: succcess
                });
                if (result.length === instanceArr.length){
                    // 说明全部都执行成功啦,直接返回
                    return fulfilled(result);
                }
            }).catch(err => {
                result.push({
                    status: 'rejected',
                    value: err
                });
                if (result.length === instanceArr.length){
                    fulfilled(result);
                }
            });
        });
    });
}

如何实现promise.race

这个API的实现思路跟之前的都差不错,它是赛马机制,所以我们需要一个全局变量,这个全局变量用来判定谁的状态是第一个发生改变的。

let customRace = (instanceArr) => {
    let isFirst = null;
    return new Promise((fulfilled, rejected) => {
        instanceArr.forEach(item => {
            item.then(succcess => {
                if (isFirst === null){
                    isFirst = true;
                    return fulfilled(succcess);
                }
            }).catch(err => {
                if (isFirst === null){
                    isFirst = true;
                    return rejected(err);
                }
            });
        });
    });
}

最后

又到了该说再见的时候了,希望这篇文章的讲解可以对你有帮助,噢对了,我最近搭建了一个交流群,里面可以自由探讨技术,如果你感兴趣,欢迎添加我的微信:18845097791,我拉你入群。那么我们下期再见啦,拜拜~~