前端 Promise 常见的应用场景

18,772 阅读7分钟

本篇将结合自身使用 ES6 Promise的情况,总结下Promise在我们项目开发中的常见的应用场景,当然,Promise 也许不是唯一选项,但是我们作为一个合格的前端开发人员,我们有必要了解它。

Promise.all

语法:Promise.all(iterable)

参数:一个可迭代对象,如Array。

返回值:

  • 如果传递的iterable为空,则是已经解决的Promise。

    Promise.all([]).then(res=>{
        console.log(res)//[]
    })
    
  • 异步解析的Promise(如果传递的Iterable不包含Promise)。 请注意,在这种情况下,Google Chrome 58返回已解决的承诺。

    Promise.all([1,2,3]).then(res=>{
        console.log(res)//[1,2,3]
    })
    
  • 当给定可迭代对象中的所有promise已解决,或者任何promise均被拒绝时,此返回的promise将被异步解析/拒绝(堆栈为空时)

    • 当给定可迭代对象中的所有promise 已解决
      let promise1 = new Promise((resolve,reject)=>{
          resolve(1)
      })
      let promise2 = new Promise((resolve,reject)=>{
          resolve(2)
      })
      
      Promise.all([promise1,promise2,3]).then(res=>{
          console.log(res)//[1,2,3]
      })
      
    • 当给定可迭代对象中的任何promise被拒绝时
      let promise1 = new Promise((resolve,reject)=>{
          resolve(1)
      })
      let promise2 = new Promise((resolve,reject)=>{
          reject(2)
      })
      
      Promise.all([promise1,promise2,3]).then(res=>{
          console.log(res)
      }).catch(err=>{
          console.log(err)//2
      })
      

    描述:

    此方法对于汇总多个promise的结果很有用, 在ES6中可以将多个Promise.all异步请求并行操作:

    1.当所有结果成功返回时按照请求顺序返回成功;

    2.当其中有一个失败方法时,则进入失败方法;

    应用场景1:多个请求结果合并在一起

    具体描述:一个页面,有多个请求,我们需求所有的请求都返回数据后再一起处理渲染

    思考:如果并发请求的话,每个请求的loading状态要单独设置,多个的话可能多个loading 重合,页面显示的内容 根据请求返回数据的快慢 有所差异,具体表现在渲染的过程,为提升用户体验,我们可以采用 所有请求返回数据后,再一起渲染,此时我们关闭请求的单独loading设置,通过Promise.all 汇总请求结果,从开始到结束,我们只设置一个 loading 即可。

    //1.获取轮播数据列表
    function getBannerList(){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('轮播数据')
            },300)
        })
    }
    
    //2.获取店铺列表
    function getStoreList(){
       return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('店铺数据')
            },500)
        })
    }
    
    //3.获取分类列表
    function getCategoryList(){
       return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('分类数据')
            },700)
        })
    }
    
    function initLoad(){
        // loading.show() //加载loading
        Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
            console.log(res)
            // loading.hide() //关闭loading
        }).catch(err=>{
            console.log(err)
            // loading.hide()//关闭loading
        })
    }
    //数据初始化    
    initLoad()
    

    应用场景2:合并请求结果并处理错误

    描述:我们需求单独处理一个请求的数据渲染和错误处理逻辑,有多个请求,我们就需要在多个地方写

    思考:我们能否把多个请求合并在一起,哪怕有的请求失败了,也返回给我们,我们只需要在一个地方处理这些数据和错误的逻辑即可。

    //1.获取轮播图数据列表
    function getBannerList(){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                // resolve('轮播图数据')
                reject('获取轮播图数据失败啦')
            },300)
        })
    }
    
    //2.获取店铺列表
    function getStoreList(){
       return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('店铺数据')
            },500)
        })
    }
    
    //3.获取分类列表
    function getCategoryList(){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve('分类数据')
            },700)
        })
    }
    
    function initLoad(){
        // loading.show()
        Promise.all([
            getBannerList().catch(err=>err),
            getStoreList().catch(err=>err),
            getCategoryList().catch(err=>err)
        ]).then(res=>{
            console.log(res) // ["获取轮播图数据失败啦", "店铺数据", "分类数据"]
            
            if(res[0] == '轮播图数据'){
                //渲染
            }else{
                //获取 轮播图数据 失败的逻辑
            }
            if(res[1] == '店铺数据'){
                //渲染
            }else{
                //获取 店铺列表数据 失败的逻辑
            }
            if(res[2] == '分类数据'){
                //渲染
            }else{
                 //获取 分类列表数据 失败的逻辑
            }
            
            // loading.hide()
        })
    }
    
    initLoad()
    

    有时候页面挂掉了,可能因为接口异常导致,或许只是一个无关紧要的接口挂掉了。那么一个接口挂掉了为什么会导致整个页面无数据呢?Promise.all告诉我们,如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),就不再执行then方法回调,以上用例 正好可以解决此种问题

    应用场景3:验证多个请求结果是否都是满足条件

    描述:在一个微信小程序项目中,做一个表单的输入内容安全验证,调用的是云函数写的方法,表单有多7个字段需要验证,都是调用的一个 内容安全校验接口,全部验证通过则 可以 进行正常的提交

    function verify1(content){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve(true)
            },200)
        })
    }
    
    function verify2(content){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve(true)
            },700)
        })
    }
    
    function verify3(content){
        return new Promise((resolve,reject)=>{
            setTimeout(function(){
                resolve(true)
            },300)
        })
    }
    
    
    
    Promise.all([verify1('校验字段1的内容'),verify2('校验字段2的内容'),verify3('校验字段3的内容')]).then(result=>{
        console.log(result)//[true, true, true]
    
        let verifyResult = result.every(item=>item)
        //验证结果
        console.log(verifyResult?'通过验证':'未通过验证')// 通过验证
    }).catch(err=>{
        console.log(err)
    })
    

    Promise.race

    语法:Promise.race(iterable)

    参数: iterable 可迭代的对象,例如Array。可迭代的。

    返回值: Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝

    描述 race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失败(rejects),这要取决于第一个完成的方式是两个中的哪个。

    如果传的迭代是空的,则返回的 promise 将永远等待。

    如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

    应用场景1:图片请求超时

    //请求某个图片资源
    function requestImg(){
        var p = new Promise(function(resolve, reject){
            var img = new Image();
            img.onload = function(){
               resolve(img);
            }
            //img.src = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/logo.a7995ad.svg~tplv-t2oaga2asx-image.image"; 正确的
            img.src = "https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-assets/v3/static/img/logo.a7995ad.svg1~tplv-t2oaga2asx-image.image";
        });
        return p;
    }
    
    //延时函数,用于给请求计时
    function timeout(){
        var p = new Promise(function(resolve, reject){
            setTimeout(function(){
                reject('图片请求超时');
            }, 5000);
        });
        return p;
    }
    
    Promise
    .race([requestImg(), timeout()])
    .then(function(results){
        console.log(results);
    })
    .catch(function(reason){
        console.log(reason);
    });
    

    应用场景2:请求超时提示

    描述:有些时候,我们前一秒刷着新闻,下一秒进入电梯后,手机页面上就会提示你 “网络不佳”

    //请求
    function request(){
        return new Promise(function(resolve, reject){
           setTimeout(()=>{
                resolve('请求成功')
           },4000)
        })
    }
    
    //请求超时提醒
    function timeout(){
        var p = new Promise(function(resolve, reject){
            setTimeout(function(){
                reject('网络不佳');
            }, 3000);
        });
        return p;
    }
    
    
    
    Promise.race([
        request(),
        timeout()
    ])
    .then(res=>{
        console.log(res)
    }).catch(err=>{
        console.log(err)//网络不佳
    })
    

    Promise.prototype.then

    应用场景1:下个请求依赖上个请求的结果

    描述:类似微信小程序的登录,首先需要 执行微信小程序的 登录 wx.login 返回了code,然后调用后端写的登录接口,传入 code ,然后返回 token ,然后每次的请求都必须携带 token,即下一次的请求依赖上一次请求返回的数据

    function A(){
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve('B依赖的数据')
            },300)
        })
    }
    function B(prams){
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(prams + 'C依赖的数据')
            },500)
        })
    }
    function C(prams){
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(prams)
            },1000)
        })
    }
    
    //我们期望的是走 try ,由于A B C模拟的请求中都是没有reject,用 try catch 捕获错误
    try{
        A().then( res=>B(res) ).then( res=>C(res) ).then( res=>{
            console.log(res)//B依赖的数据C依赖的数据
        })   
    } catch(e){
        
    }
    

    应用场景2:中间件功能使用

    描述:接口返回的数据量比较大,在一个then 里面处理 显得臃肿,多个渲染数据分别给个then,让其各司其职

    //模拟后端返回的数据
    
    let result = {
        bannerList:[
            {img:'轮播图地址'}
        //...
        ],
        storeList:[
            {name:'店铺列表'}
        //...
        ],
        categoryList:[
            {name:'分类列表'}
        //...
        ],
        //...
    }
    
    function getInfo(){
        return new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(result)
            },500)
        })
    }
    
    getInfo().then(res=>{
    
        let { bannerList } = res
        //渲染轮播图
        console.log(bannerList)
        return res
    }).then(res=>{
        
        let { storeList } = res
        //渲染店铺列表
        console.log(storeList)
        return res
    }).then(res=>{
        let { categoryList } = res
        console.log(categoryList)
        //渲染分类列表
        
        return res
    })
    

    参考资料:

    中文 Promise - JavaScript | MDN

    英文 Promise - JavaScript | MDN

    结语

    如果你有更好的点子,欢迎留言

    文中若有不准确或错误的地方,欢迎指出

    往期文章 :

    前端代码优化实用篇

    前端开发中实用的工具方法