Promise的妙用

这是一篇初级文章。

来源于我一个新入行的同事给我发了一段程序:

async checkLogin() {
    const { data } = await this.$axios.getLogined();
    if (data.manager) {
      this.$store.commit("setLoginInfo", data.manager);
      return true;
    } else {
      return false;
    }
}

因为我从来没想过把axios请求放到await里面去, 初次看这种写法还是很惊讶的。我问他为什么不用then要用await这样写,回答说这样写比较清楚,比写很多then()清楚。

如果是简单逻辑,当然怎么写其实都差不多,但是鉴于现在每个页面基本都要用上几个或者更多的数据接口,我们就要进一步的考虑各自写法的不同性能问题。

1、Axios的then是什么

首先我们来讲一下Axios的then是什么?这是写给我自己的,因为我最开始并不了解这背后的道理,基本写完了一个项目之后,我才回过头去看为什么axios的请求都要用then来做后续处理。我相信也有前端小伙伴接触到then函数也是先从axios这类大的类库开始的,很少我们会在自己的代码中用到这个。

then函数其实是Promise对象的一个功能,一个

axios.get("xxx").then((res)={})

其实是下面两行代码的连写:

let pm = axios.get("xxx"); //pm是一个Promise对象,被get函数返回回来
pm.then((res)={
  console.log("2");
})
console.log("1");

所以,axios的大量then的写法其实是由js的promise对象的特性支撑的,由于Promise机制被广泛接受和应用,所以我们看到then函数可以大概率认为里面是Promise机制。

1.1 Promise机制是什么

那promise是什么,promise是一种特殊的对象,因为说对象大家容易和程序里常用的用来表示复杂结构的object连在一起,我更愿意叫他“机制",一种系统级别的提供给开发人员用的机制。

Promise用来表示一个异步过程和他返回的数据 。异步的意思是在主流程里叉出来一条路并行执行,拿上面的代码说,就是主线流程执行到第一行的时候,叉出来一条路执行axios.get, 这时主线流程继续执行console.log("1"),而axios在完成get之后,调用then函数里面的匿名函数执行console.log("2")。猜一猜一般情况下是先打印1还是先打印2?通常本地执行都快一些,所以一般是先打印1,再打印2,当然这个只是通用情况,有时promise里的流程也会更快。

 这里的then,可以理解成当。。。的时候执行,因为axios请求一个数据,通常受网络环境等影响不知道什么时候可以完成,所以then里的函数是在返回时执行的,具体执行时间不确定。

1.2 await之后的执行顺序

上面讲的是then的情况,那await是什么情况呢,await对应的写法是这样:

let pm = axios.get("xxx"); //pm是一个Promise对象,被get函数返回回来
await res = pm;
console.log("2");
console.log("1");

对应的时间线是

 主线程执行await,等待axios的get完成。这样的问题是本来可以主线执行的一些功能只能被动的等待get这个部分完成才能继续进行。

1.3 promise引入对整体app效果的影响

上面的文字,大家可能在入门的时候,很多培训书也都会讲到这点,大家会有个初步的印象,axios是用了promise,promise是异步请求,await可以让异步请求变得同步。

那在实际项目中有什么影响呢?考虑一个有5个axios请求的页面

完全并发执行效果是这样的,五个请求被依次启动后,按数据和请求时间不同,近似随机的返回回来:

 如果全部用await的话,是这样

 看起来倒是规整了很多,但是整体花费的时间就更长了,可见:

1)如果不用await,因为所有请求都是异步完成的,再加上网络请求的时效性通常不确定,因此,会导致很多事件的发生变得有些混乱,特别是在vue/react等环境里面,数据的混乱通常又即时的反应到了页面上,就会出现很多诡异的情况,让人摸不着头脑。

2)如果用await,确实,很多流程和逻辑我们可以通过强制同步来固定下来,但是这时,因为数据请求通常很多,强制同步会造成页面加载时间拉长。而且一旦某个出错,那整个这个链条就断了。

这两种极端情况下,各自的问题是什么,大家可以在脑海中再回顾一下自己遇到过哪种问题,因为我们在了解到这两种极端情况之后,在开发中实际要做一件很重要的事情,就是按自己规划的加载顺序,把数据调用规划成这样:

 按数据加载的顺序,按关键的数据请求的依赖关系里出来,把可以并发的并发执行,把需要等待的等待执行,整个逻辑还是推荐用then串起来,类似这样:

axios.get("a").then(resA=>{
   handleA(resA);
   {//并发执行
     axios.get("b");
     axios.get("c");
   }.then((resB,resC)=>{
      handleB(resB);
      handleC(resC):
      {//并发执行
        axios.get("d");
        axios.get("e");
      }.then((resD,resE)=>{
         handleD(resD);
         handleE(resE):
      }
   }
}

实现复杂的并发设计

上面的这个伪代码里,我们标注了一个并发执行b和c和一个并发执行d和e的逻辑,实际怎么用呢,我们会用到一个函数叫Promise.all, 这时一个Promise类的类函数,all是所有的意思,那这个的用法就很显而易见了,就是等待所有的Promise执行完成。

那上面的并发执行b和c就可以写成

Promise.all(axios.get("b"),axios.get("c")).then(res=>{
    //此时res是包含了两个axios请求结果的数组;
    handleB(res[0]);
    handleC(res[1]);
}

有了all函数的帮助,我们就可以轻松的实现,并发执行两个请求,并且等待他们都完成的这个目的,而不用考虑两个请求之间的先后关系,达到束型的目的。

实际上,查看Promise类的帮助文档,我们还可以发现更多有趣的函数,他们都是用来处理并发过程的: 

allSettled:和all类似,但是不会因为某个失败而reject(因此,all是要求所有成功的)。

any:任意一个执行成功成功,所有失败失败

race:任意一个执行成功成功,任意一个执行失败失败。

通过这些函数的引用,我们可以更好的给axios等异步请求做规划,更好的帮助页面有序而快速的加载数据。

哪些地方还能用到Promise

上面我们讲到了Promise的并发控制,其实,有一个更简单的事实,不知道大家有没有发现。

那就是其实.then()函数实际可以分开写的。

let pm = axios.get("xxx"); //pm是一个Promise对象,被get函数返回回来
pm.then((res)={
  console.log("2");
})
console.log("1");

有人可能觉得我在讲废话。但是这个对我们书写代码有很大的帮助。

使用promise简化分支处理

有些页面我们可能会加载不同的数据来处理,通常可能我们会写成这样:

if (type == 'a') {
  axios.get("a").then((res)=>{
    displayPart1(res.data);
    displayPart2(res.data);
    displayPart3(res.data);
  });
}else if (type == 'b') {
  axios.get("b").then((res)=>{
    displayPart1(res.data);
    displayPart2(res.data);
    displayPart3(res.data);
  });
}else if (type == 'c') {
  axios.get("c").then((res)=>{
    displayPart1(res.data);
    displayPart2(res.data);
    displayPart3(res.data);
  });
}else if (type == 'd') {
  axios.get("d").then((res)=>{
    displayPart1(res.data);
    displayPart2(res.data);
    displayPart3(res.data);
  });
}else if (type == 'e') {
  axios.get("e").then((res)=>{
    displayPart1(res.data);
    displayPart2(res.data);
    displayPart3(res.data);
  });
}

这是一个新手常见的会写的代码,会因为5种不同的处理逻辑,复制5份代码,实际因为显示部分逻辑通常更复杂,这样的代码会更长。一种优化方式是:

var display = function(res){
  displayPart1(res.data);
  displayPart2(res.data);
  displayPart3(res.data);
}

if (type == 'a') {
  axios.get("a").then(display);
}else if (type == 'b') {
  axios.get("b").then(display);
}else if (type == 'c') {
  axios.get("c").then(display);
}else if (type == 'd') {
  axios.get("d").then(display);
}else if (type == 'e') {
  axios.get("e").then(display);
}

那我推荐还有另外一种:

var p;
if (type == 'a') {
  p= axios.get("a")
}else if (type == 'b') {
  p= axios.get("b")
}else if (type == 'c') {
  p= axios.get("c")
}else if (type == 'd') {
  p= axios.get("d")
}else if (type == 'e') {
  p= axios.get("e")
}

p.then((res)=>{
  displayPart1(res.data);
  displayPart2(res.data);
  displayPart3(res.data);
})

可以看到,在这种优化办法里,我们在if阶段,只是构建了不同的promise,请求了不同的数据,而在第二个阶段,我们通过一个通用的then函数来完成了显示部分的逻辑。

这样的好处是数据部分和显示部分的逻辑清晰,先后顺序清楚。而且一旦我们知晓了这种方法,我们还能做很多事情。

使用promise来统一缓存和远程请求

有时,我们会在localstorage里面存储很多缓存数据,有了上面的点子,我们可以做成这样:

var p 

var cache = localStorage.getItem("cache");

if (cache !== undefined) {
  p = Promise.resolve({
    data:JSON.parse(cache);
  })
}else{
  p = axios.getA("a").then(res=>{
    localStorage.setItem("cache",JSON.stringfy(res.data));
    return res;
  }
}

p.then((res)=>{
   display... 
})

 这样,在处理显示逻辑的时候,我们不需要做任何关于数据来源的区分逻辑,里面用到了Promise.resolve函数,他的作用是直接返回一个成功的promise,数据就是传入参数,为了和axios返回保持一致,我们返回了一个对象,对象包含data属性,模拟axios返回的结构。

这种结构我们可以进一步将数据获取部分放到vuex里作为helper或者其他公共类里面,这样数据层和展示层就可以更好的进行分离。这样页面里的代码就简化成了这样:

import storage from "...";

var p = storage.getA();

p.then((res)=>{
   display... 
})

可以看见,对于页面开发人员来说,这样处理后,对于数据来源的区别就完全不用考虑了。

使用promise来统一需要输入和不需要输入的情况

我们通常会遇到会需要对话框要求用户输入一个东西的时候,而有时,这些参数又是程序带入的,这样的情况我们也可以使用promise来统一

function request(name){
  if(name!=undefined){
    return Promise.resolve(name);
  }else{
    return new Promise((resolve,reject)=>{
       var input = prompt("请输入A");
       if(input){
         resolve(input);
       }else{
         reject();
       }
    });
  }
}

request();//要求用户输入
request("指定名称"): //使用指定名称

总结:使用Promise可以轻松的给页面流程束型

我经常会写这样的代码:

  axios
    .getCorp()
    .then(noError)
    .then((data) => {
      form.value = data;
    })
    .catch(showErrMessage);

虽然有时会想应该再做一层封装来提炼通用部分,但是我更愿意在axios的流程中保留这种用then串起来的流程,因为这样会让我清楚,围绕数据调用我们用了几层来处理返回数据,错误又是怎么处理的。

今天从axios的then开始,我们讲了promise的一些好玩的用法,其实目的都在这个then上,串起来的then就是串起来的核心业务流程,兼顾了程序的复用,又以一种简单的方式直观展示了程序的逻辑,你觉得有趣么?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百家饭OpenAPI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值