美文网首页
一文搞懂JS系列(八)之轻松搞懂Promise

一文搞懂JS系列(八)之轻松搞懂Promise

作者: 辉夜真是太可爱啦 | 来源:发表于2021-05-26 16:38 被阅读0次

    写在最前面:这是我写的一个一文搞懂JS系列专题。文章清晰易懂,会将会将关联的只是串联在一起,形成自己独立的知识脉络整个合集读完相信你也一定会有所收获。写作不易,希望您能给我点个赞

    合集地址:一文搞懂JS系列专题

    概览

    使用环境

    Promise 本质上是一个构造函数,因为使用 new 关键字创建,它是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,属于 ES6 中的一个概念。

    简单来说,Promise 其实就是一个异步操作的容器,可以获取用来优雅地获取异步操作的结果。当然你也可以放同步操作,可以,但是不建议。毕竟同步任务一般直接正常书写即可,没必要套个 Promise 的壳子。

    所以, Promise 一般用于 $ajax 数据请求,即用于封装 http 请求,例如下面的方式(用的 axios 做的数据请求)

    // 向后台发送数据
    export const postData= (params) => {
      return new Promise((resolve, reject) => {
        axios.post('/xxxUrl', params).then(res=>{
          resolve(res)})
          .catch(error=>{reject(error)});
      })
    };
    

    三种状态

    Promise 实例有三种状态:

    • pending

      进行中,这是 Promise 实例创建后的一个初始态。

    • fulfilled

      已成功。在执行器中调用 resolve 后,达成的状态。

    • rejected

      已失败。在执行器中调用 reject 后,达成的状态。

    这当然也很好理解,就和你做事情一样,一开始是进行中,最后,只有成功或者失败。

    就拿上面的例子来说, postData 就是一个 Promise的实例对象,new Promise 之后,它的状态就是 pending ,只有当 axios.post 即网络请求成功或者失败了以后,它的状态才会变更,变更为 fulfilledrejected ,而这个第二种状态,取决于网络请求的结果。

    Promise.prototype

    • Promise.prototype.then()

      then 方法简单理解就是 resolve() 回调之后的产物,即 已成功 状态下的回调函数。

    • Promise.prototype.catch()

      catch 方法简单理解就是 reject() 回调之后的产物,即 已失败 状态下的回调函数。

    • Promise.prototype.finally()

      finally 方法简单理解就是 无论成功或失败 ,都会执行的回调函数。

    特性

    1. 立即执行

    Promise 实例创建后,执行器里的逻辑会立刻执行,在执行的过程中,根据异步返回的结果,决定如何使用 resolvereject 来改变 Promise 实例的状态。如何理解下面的这句话,先来看一个例子

      const promise = new Promise(function(resolve, reject) {
        console.log('start');
        if (true){
          resolve('success');
        } else {
          reject('error');
        }
        console.log('end');
      });
      
      promise.then(res=>{
        console.log(res);
      })
    

    根据上面的学习,我们可以知道,在一开始控制台便会输出 start ,毕竟 Promise 在创建以后便会立即执行,然后输出 end ,等到处理完所有同步任务以后,再进行处理异步任务,因为走的是 resolve() ,所以最后输出 success,结果如下

    start => end => success
    

    2. 承诺

    它另外也有自己一个很大的特点,那就是不受外界的影响。

    只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

    3. 结果唯一性

    成功会触发 then,而失败会触发 catch ,状态不一致则会获取不到结果。可以看下下面的例子

    const promise = new Promise(function(resolve, reject) {
        console.log('start');
        if(true){
            resolve('success');
        }else{
            reject('error');   
        }
        console.log('end');
    });
    
    promise.catch(error=>{
        console.log(error);
    })
    

    由于 Promise 中是 resolve 回调,即成功状态,所以,只会走 then() 而不会触发 catch

    所以,程序的运行结果为 start => endcatch() 根本不会执行。

    当然,不仅如此, Promise 一旦状态改变,就不会再变。我们来改写下上面的代码,为了加以区分,我们先使用 reject() 然后再调用 resolve()

      const promise = new Promise(function(resolve, reject) {
        console.log('start');
        reject('error');
        resolve('success');
        console.log('end');
      });
      
      promise.then(res=>{
        console.log(res);
      }).catch(error=>{
        console.log(error);
      })
    

    由于状态一但改变,就不会再变,所以,它的状态有且只有且一直是初次改变的结果,而首次执行 reject ,即失败状态。

    所以,上面的输出结果为 start => end => error

    Promise.all()

    Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

    例如定义了三个 postData 的实例方法,代码如下

    const dataList = Promise.all([postData1(),postData2(),postData3()])
    

    只有所有请求的状态都成功 ,dataList的状态才会变成 fulfilled ,此时 postData1, postData2, postData3的返回值组成一个数组,传递给dataList的回调函数。

    只要有一个请求失败,那么,dataList的状态就会变成 rejected ,此时第一个被reject的实例的返回值,会传递给dataList的回调函数。

    使用场景:

    举个例子,三个接口分别是拉取语文,数学,英语三科成绩的接口,那么,我们需要通过这三个接口的返回,计算学生最后成绩的总分,此时用 all() 就很合理,因为三个接口的值缺一不可,如果有一个发生错误,就得不到总分,就会走 catch() ,然后,提示是哪一科的成绩数据得不到,影响了最终的总分计算。

    Promise.race()

    Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

    const dataList = Promise.rece([postData1(),postData2(),postData3()])
    

    上面代码中,三个 Promise 实例,只要谁率先改变状态,那么,dataList 的状态也就跟着改变,相当于就是谁快,我就用谁

    使用场景:

    ① 当一个接口有三个请求接口地址,请求的数据是一致的时候,为了保证接口的最快速度匹配,可以使用这个方法。

    ② 在Promise实例中,放入一个延时器函数, setTimeout(() => reject(new Error('request timeout')), 5000) ,可以通过它来设置这个接口的,相当于 postData1() 必须在5秒内完成,否则会直接失败

    const dataList = Promise.race([
      postData1(),
      new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
      })
    ]);
    

    Promise.allSettled()

    Promise.allSettled() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。(ES 2020 特性)

    只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,最终包装成一个数组返回。并且它无论参数实例是成功或失败,始终只会走 .then() ,没有 .catch() ,成功之后返回的结果如下所示:

    
    [
       { status: 'fulfilled', value: 42 },
       { status: 'rejected', reason: -1 }
    ]
    

    每个对象都有 status 属性,该属性的值只可能是字符串 fulfilled 或字符串 rejectedfulfilled时,对象有 value 属性, rejected 时有 reason 属性,对应两种状态的返回值。

    所以,可以通过 status 方法进行区分参数实例的成功或失败。

    success = results.filter(p => p.status === 'fulfilled')
    error = results.filter(p => p.status === 'rejected')
    

    使用场景:

    并不关心接口的结果,只关心这些操作有没有结束。

    Promise.any()

    Promise.any() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。(ES 2021 特性)

    只要参数实例有一个成功,包装实例就会变成成功状态;只有当所有参数实例都失败,包装实例才会变成失败状态。

    使用场景:

    未想出,待补充

    回调地狱

    Promise 的功能确实很强大,但有的时候,我们的接口是要按顺序执行,比方说我们要先拉取第一个接口,用第一个接口的参数去拉取第二个接口,然后再去拉第三个接口,此时,必须按顺序执行

    getData1('').then(res1=>{
        getData2(res1.data.id).then(res2)=>{
            getData3(res2.data.id).then(res3=>{
    
            })
        }
    })
    

    当然,随着项目的复杂度,有时候可能需要四层五层,虽然这种情况应该比较少,这也就是所谓的回调地狱,其中又夹杂着闭包的概念,内部可以访问外层的结果,一层又一层的接口返回数据,维护的时候头都看晕了。

    修改的时候还要先看到底是改第几层的代码,以防止改错地方。

    当然,下一篇博客会来讲述下 Generator ,以及最终最优雅地方式 async await

    最后,欢迎大家关注我的个人公众号 前端大食堂

    参考

    阮一峰 ECMAScript 6 入门 - Promise

    系列目录

    相关文章

      网友评论

          本文标题:一文搞懂JS系列(八)之轻松搞懂Promise

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