美文网首页
JS异步编程(5)-async/await

JS异步编程(5)-async/await

作者: Johnson杰 | 来源:发表于2021-06-21 10:58 被阅读0次

    async/await 是什么

    了解和使用 async 之前,需要提前了解以下部分:

    • Event loop
    • Promise
    • Generator

    async/awaitES7 专门为异步编程设计的语法,本质上是 Generator 的语法糖
    在继承了 Generator 可分段执行程序的能力之外,弥补了 Generator 本身的不足

    // async/await 之前
    // 主要还是使用 Promise 链式调用的方式,形式上还是“链式调用” + “回调函数”
    function task() {
        task1()
        .then(() => task2)
        .then(() => task3)
    }
    
    // async/await 之后
    // 真正同时做到了“异步任务按序执行”的“顺序执行写法”
    async function task() {
        await task1()
        await task2()
        ...
    }
    

    基本执行流程:

    1. async 函数 task 执行到第一个 await 异步任务 task1,会将执行线程让给 task1
    2. 直到 task1 正常返回,才会重新获得执行线程,继续执行第二个 await 异步任务 task2
    3. 依次类推

    重点主要分为两个部分:async 关键字和 await 关键字

    async 关键字

    async 类似 Generator 中的 *,用于将函数定义为 async 函数
    成为 AsyncFunction 构造函数的实例
    AsyncFunction 参考

    async 函数具备几个特点

    1. 语义清晰明确
    2. 返回 Promise 对象
    3. 自带执行器,开箱即用
    4. 可以被 try catch 捕获代码错误

    语义清晰明确

    相比于 *yieldasync 异步,await 等待,清晰明确,没有歧义

    返回 Promise 对象

    返回值是 Promise 类型的对象,方便使用 Promise API
    便于各类异步场景的运用

    • 如果 async 函数返回的不是一个 Promise 对象,会使用 Promise.resolve() 进行处理返回
    • 如果 async 函数返回一个 Promise 对象,以这个对象为准
    • 如果 async 函数报错或者 reject,会返回一个 rejectPromise 对象

    自带执行器,开箱即用

    不同于 Generator 函数需要使用 co 之类的函数库封装执行器
    async 开箱即用,不需要额外的封装

    可以被 try catch 捕获代码错误

    之前的异步编程方案,例如 Promise 在异步任务中的代码错误,无法被 try catch 捕获
    但是在 async 函数中,代码执行错误可以被 try catch 捕获了

    // Promise 
    function promiseFun() {
        try {
          errFun()
        } catch(err) {
          console.log('err', err) // 不会执行
        }
    }
    
    // async
    async function asyncFun() {
        try {
          await errFun()
        } catch(err) {
          console.log('err', err) // 会执行
        }
    }
    
    // 模拟异步代码错误
    async function errFun() {
      return Promise.resolve().then(() => {
        '123'.filter(item => item.name) // 这里代码执行错误
      })
    }
    

    await 关键字

    await 类似 Generator 中的 await,用于移交执行线程给其他函数

    await 只能在 async 函数中使用

    await 只能在 async 函数中使用,不能在其他类型函数中使用
    否则会报错!

    await 顺序执行会被 rejected 的 Promise 阻断

    如下所示:
    await 对应的表达式或者函数返回值,如果是一个 rejected 状态 的 Promise
    async 函数会被中断执行

    async function test(){
        await Promise.resolve(1);
        await Promise.reject(2);
        await Promise.resolve(3); // 程序无法执行
        return 'done';
    }
    

    因此,async/await 的一个重头戏就是错误处理

    async/await 在使用中的问题

    async/await 在使用中的问题基本有两个

    1. 错误处理
    2. 在循环迭代中的使用

    async 错误处理

    async 函数错误的基本处理方式有 try catchPromise catch 两种方式

    // try catch
    async function fun1() {
      try {
          await somethingThatReturnsAPromise()
      } catch(err) {
          console.log(err)
      }
    }
    
    // Promise catch
    async function fun2() {
      await somethingThatReturnsAPromise().catch((err) => {
        console.log(err);
      });
    }
    

    基于以上的两种方式,延伸出两种对应的优化方案

    循环迭代中使用 async

    我们平时常用的循环迭代方法,在与 async/await 结合使用时,会出现一些意料之外的情况

    • work:按时间间隔执行打印
    • no work:同时打印

    下面是各个方案的结果:

    • for 循环:work
    • for in:work
    • for of:work
    • while:work
    • forEach:no work
    • map:no work
    • filter:no work
    • reduce:no work
    // 工具函数
    // 期望通过循环 list 执行 setTimeoutFun
    // 实现每隔 1000ms 打印日志
    const list = [1000, 1000, 1000]
    const setTimeoutFun = (num) => {
        return new Promise((resolve) => {
            setTimeout(() => {
                console.log(num, new Date())
                resolve(num)
            }, num)
        })
    }
    
    // for 循环
    async function forFun() {
        for (let i = 0; i < list.length; i++) {
            await setTimeoutFun(list[i])
        }
    }
    
    // for in
    async function forInFun() {
        for (const i in list) {
            await setTimeoutFun(list[i])
        }
    }
    
    // for of
    async function forOfFun() {
        for (const num of list) {
            await setTimeoutFun(num)
        }
    }
    
    // while
    async function whileFun() {
        let i = 0
        while(i <= list.length - 1) {
            await setTimeoutFun(list[i])
            i += 1
        }
    }
    
    // forEach
    function forEachFun() {
        list.forEach(async (num) => {
            await setTimeoutFun(num)
        })
    }
    
    // map
    function mapFun() {
        list.map(async (num) => {
            await setTimeoutFun(num)
        })
    }
    
    // filter
    function filterFun() {
        list.filter(async (num) => {
            await setTimeoutFun(num)
        })
    }
    
    // reduce
    function reduceFun() {
        list.reduce(async (pre, next, index) => {
            return await setTimeoutFun(next)
        }, Promise.resolve())
    }
    

    forEachmapfilter 通过查看 polyfill 源码,可知
    内部是通过 while 循环 的方式调用 callback,这个循环没有使用 async/await,不会等待异步任务执行完成
    因此 forEachmapfilter 会出现近似于同时执行多个异步任务的情况

    reduce 比较特殊
    通过查看 reduce polyfill 源码,可知 reduce 也是通过 while 循环的方式调用 callback
    所以原理上和 forEachmapfilter 一样,也是同时执行多个异步任务

    但是可以通过一些优化来实现 reduce + async/await

    1. async 返回一个 Promise 对象,所以 callbacktotal 需要处理 then 函数
    2. 第一个 total 值或者 reduce 初始值需要加工成 Promise 对象
      否则会报错
    // reduce + async/await
    function reduceFun() {
        list.reduce(async (pre, next, index) => {
            return await pre.then(() => setTimeoutFun(next))
        }, Promise.resolve())
    }
    

    这里有一个有趣的点——为什么这么写 reduce 生效forEach 不生效

    // 这里 forEachFun 依旧是同时执行
    function forEachFun() {
        list.forEach(async (num) => {
            await Promise.resolve(num).then(() => setTimeoutFun(num))
        })
    }
    
    // 这里 reduceFun 依旧可以等待上一个任务执行完成
    function reduceFun() {
        list.reduce(async (pre, next, index) => {
            // return await Promise.resolve().then(() => setTimeoutFun(next)) // 不使用 pre 就同步进行
            return await Promise.resolve(pre).then(() => setTimeoutFun(next)) // 使用 pre 就依次进行
        }, Promise.resolve())
    }
    

    从网上找到的观点是:
    如果 reduce callbacktotal 参数第一个出现并且参与计算
    就可以让异步任务依次进行

    听起来很扯,也没有从 MDN 说明和源码 polyfill 上找到合理的解释

    相关文章

      网友评论

          本文标题:JS异步编程(5)-async/await

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