async 和 await

作者: peakol | 来源:发表于2018-05-25 19:56 被阅读0次

    这段时间一直在重构项目,遇见很多请求“高并发”,因为涉及到多个请求又或者多个连续请求。之所以给高并发带上引号,因为面对大量请求的时候,我们需要调整好姿势,怎样去好好的去写异步回调,弄清各个请求的顺序,稍微不注意可能就掉坑了,可能调试半天看着vue-devtool控制台打印的自己以为的“完美的数据”,但是页面各种显示不听话。哦,那说明你掉坑了!
    理清好执行的先后顺序,其次再写回调的时候我们就要选好正确的方法和正确的姿势,这样才不会造成将来你写的代码你认不清的尴尬,同时感觉代码一目了然!
    同时考虑到异步回调我们需要理解一些知识:
    js的运行机制:
    在代码运行时会形成任务队列,分为同步任务队列和异步任务对列,同步队列优先加载,异步任务队列会形成队列任务池,定时器不会一下加载到异步任务,而是在设定的时间后加载到异步任务,即使设置为0,浏览器也有它的响应时间,以前是10ms.现在是4ms.异步任务包括dom 事件,定时器,promise
    首先作为异步回调的功能和作用不去作过多解释,对于js这种单线程异步回调是性能优化的一些点。
    异步回调我觉得主要有两方面作用:

    • 不阻碍程序运行,将一些延时较久的函数异步执行,不妨碍正常同步运行的代码;
    • 一个函数必须在某个函数执行完成后才能运行,比如说需要用函数执行完后的某些数据;
      首先写异步回调的姿势大概有这样几种
    1. 直接函数套函数(通俗的讲)
      就像这样
    function fn(callback) {
      setTimeout(() => {
        callback()
      }, 1000);
    }
    function f1() {
      console.log('f1')
    }
    fn(f1)
    // fn
    // f1
    

    这样我们感觉还行,还不错,还能接受,那如果有另外一个f2函数,f1像fn那样,在多个f3:

    function fn(callback) {
      setTimeout(() => {
        callback(f2)
      }, 1000);
      console.log('fn')
    }
    function f1(callback) {
      setTimeout(() => {
        callback(f3)
      }, 1000);
      console.log('f1')
    }
    function f2(callback) {
      setTimeout(() => {
        callback(f3)
      }, 1000);
      console.log('f2')
    }
    function f3() {
      console.log('f3');
    }
    fn(f1)
    // fn
    // f1
    // f2
    //f3
    

    这样看着代码也没这么乱,但是感觉把自己调懵了,如果想看出它的一些过程,我们将函数改写一下

    function fn(callback) {
      setTimeout(() => {
        callback((callback) => {
          setTimeout(() => {
            callback()
          }, 1000);
          console.log('f2')
        })
      }, 1000);
      console.log('fn')
    }
    fn((callback) => {
      setTimeout(() => {
        callback((callback) => {
          console.log('f3')
        })
      }, 1000);
      console.log('f1')
    })
    // fn
    // f1
    // f2
    // f3
    

    如果连起来看,分清之前的f1,f2,f3函数很困难吧,这还只是三个回调函数,有时候不仅仅这些吧,如果再来两个,咋样???
    所以传统方法晦涩难懂。。。。

    1. 第二种方法看起来可能就比较爽了------Promise
      Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件,更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
      所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
      前两句话是摘自阮一峰老师的《es6入门》
      我理解的Promise其实就是给你封装好的一个异步对象,本身有resolve和reject参数,当然也是函数,其实这里叫函数也不太好,我们不如说叫“自定义钩子”,当然这个还不像我们常说的钩子函数那样。Promise对象外部暴露的了两个分别对应的是成功之后的then函数,另一个是reject函数。说白了这两个函数是由用来设计resolve和reject函数的。Promise只负责将你写的函数在内部调用,同时将他的返回值有两个(这里说的两个是两种结果)传递出来,成功之后自然就是resolve(data),失败是reject(data),当然我们写resolve的代码块其实就是Promise实例执行完后对应的then函数的执行,当然data就是then函数回调的参数,当然reject和catch()也是一样的道理。其实看着这么像函数传参的过程,也这么像“依赖注入”的赶脚。
      不说别的了,直接将上面函数改写一下:
    function fn() {
      console.log('fn');
      return new Promise(resolve => {
        setTimeout(() => {
          resolve()
        }, 1000);
      })
    }
    function f1() {
      console.log('f1')
      return new Promise(resolve => {
        setTimeout(() => {
          resolve()
        }, 1000);
      })
    }
    function f2() {
      console.log('f2')
      return new Promise(resolve2 => {
        setTimeout(() => {
          resolve2()
        }, 1000);
      })
    }
    function f3() {
      console.log('f3');
    }
    fn().then(() => {
      f1().then(() => {
        f2().then(() => {
          f3()
        })
      })
    })
    // fn
    // f1
    // f2
    // f3
    

    等到讲aync和await时再好好讲讲Promise,今天的项目有一个特点就被埋坑了
    有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易,看着上面的代码是不是清爽多了,但是感觉函数一多结构看着似乎也不那么友好,同时Promise也有弊端就是获取错误信息的时候,这些就不赘述了,网上应该有很多介绍的,今天的主角是async 和 await

    1. async 和 await
      这两个方法其实就是Generator 函数的语法糖。详情请见阮一峰的es6
      先说说背景,今天做一个黑名单的需求:
      拉取软件列表的时候,首先我需要对list中存在的黑名单来个请求黑名单的请求,同时请求完成后我们需要将黑名单list存储在vue的data对象里,然后再通过对list进行过滤,对是黑名单的item进行软件图标的置灰操作,同时在不同的分类我们还需要进行判断是否进行过黑名单的操作,若果进行过就可以对本分组进行锁定和解锁操作,同时后续操作我们还需要拉取一个已经锁定分组的集合,同时是判定当前分组是否属于已经锁定分组,相应的在页面中对当前分组是显示解锁按钮还是锁定按钮
      鉴于涉及到这么多的请求,一个请求完成后需要完成几个请求才能进行相应参数,同时需要请求的先后顺序很明确。
      刚开始考虑就用Promise,因为封装的ajax方法就是基于axios的,但涉及到这么多连续请求先别说结构不友好,可能出错你都不知道怎么出的,所以我打算用async和await:
      当然咱们还是先把上面的那个例子改一下再说项目中的问题:
    function f1() {
      console.log('f1')
      return new Promise(resolve => {
        setTimeout(() => {
          resolve()
        }, 1000);
      })
    }
    function f2() {
      console.log('f2')
      return new Promise(resolve2 => {
        setTimeout(() => {
          resolve2()
        }, 1000);
      })
    }
    function f3() {
      console.log('f3');
    }
    async function fn() {
      console.log('fn');
      await f1()
      await f2()
      f3()
    }
    fn()
    // fn
    // f1
    // f2
    // f3
    

    这样写代码是不是看起来清爽多了!
    今天写逻辑的时候犯了一个影响智商的错看代码和执行效果
    在公司写的代码不变贴上来,所以就来模拟一下函数的执行

      async function fn() {
        await f1()
        console.log('fn')
      }
      function f1() {
        new Promise(resolve => {
          setTimeout(() => {
            console.log('f1')
            resolve()
          }, 1000);
        })
      }
      fn()
    // fn
    // f1
    
    async function fn() {
        await f1()
        console.log('fn')
      }
      function f1() {
       return new Promise(resolve => {
          setTimeout(() => {
            console.log('f1')
            resolve()
          }, 1000);
        })
      }
      fn()
    // f1
    // fn
    

    看看两次执行的结果
    第一个函数块写的时候完全不按异步执行去操作,总是像一般函数那样,await当成了异步队列,我只想说明注意的一个点事用Promise时的return。
    我们应该了解下面这些事:
    Promise有一个性质就是立即执行,我们需要将它外包一层函数(就叫fn),当我们await fn(),请记住fn只是为了不让promise立即执行,所以我们一定得在fn函数中返回promise,如果不返回,应该知道没有返回值的函数的执行结果是undefined,还执行个毛线呀!
    记住函数没有返回值执行结果就是undefined!!!!
    同时还有一个比较常见的问题:

    async getList() {
            fetchList(this.listQuery).then(
              ({data}) => {
              let list = data.list
              this.softTotalNum = data.total
              // 获取黑名单list
              await this.getBlackList()
              list.map(item => {
                if (this.soft_ids.indexOf(item.soft_id) >= 0) {
                  item['is_hidden'] = 1
                }
                return item
              })
              this.list = list;
            })
          },
    
    这时候报的错

    其实应该这样写

    getList() {
            fetchList(this.listQuery).then(
             async ({data}) => {
              let list = data.list
              this.softTotalNum = data.total
              // 获取黑名单list
              await this.getBlackList()
              list.map(item => {
                if (this.soft_ids.indexOf(item.soft_id) >= 0) {
                  item['is_hidden'] = 1
                }
                return item
              })
              this.list = list;
            })
          },
    

    一定要在你用的await的最近的父级用async声明异步函数,其他的顶级父级没有必要写async

    今天遇到坑后总结了下面三点:

    1. async是一个异步函数声明词,await必须在async函数中使用,await后面应该你用一个延时函数,当然你用一个一般函数也行,就是立即执行而已。根据单词的字面意思,await我们可以理解为必须等我后面的函数执行完之后,下面的代码才能运行。
    2. await的最近父级必须是async函数,否则会报“await is a reserved word”错。
    3. await对应的如果类似于promise的函数,鉴于promise的立即执行的特点,我们需要将它外包一层函数(就叫fn),当我们await fn(),请记住fn只是问了不让promise立即执行,所以我们一定得在fn函数中返回promise,如果不返回,应该知道没有返回值的函数的执行结果是undefined,还执行个毛线呀!
      以上就是一些按async和await的一些知识,当然这是es7的东西,记得babel编译!!!

    相关文章

      网友评论

        本文标题:async 和 await

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