• 我们为什么需要async/await ?


    async 是什么 & async的基本用法

    async function 声明用于定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果。但是如果你的代码使用了异步函数,它的语法和结构会更像是标准的同步函数。 引用自MDN。

    js的方法和语法糖多数都是语义化的,从字面意思上来说,async代表异步的,用来表示一个异步的函数,返回一个promise,可以使用then方法添加回调。 可以看下这个例子:

    const foo = async () => {
      return { name: '芬达' }
     }
     console.log(foo())//Promise

     可以看到返回的是promise哦。这样不就可以愉快的写then式回调了嘛。 偷笑! 简单来说,只要使用了 async, 就会返回一个 promise

    await 与 async 搭配的基本用法

    await 在等谁

    await在英汉词典中是动词 等候 的意思,用法如下:

    // 只能用在async函数中
    const val = await promise

    await可以让js进行等待,直到promise执行并返回结果时才会继续往下执行。可以看一个小例子:

     
    const foo2 = async () => {
     
    const promise = new Promise((reslove, reject) => {
     
    setTimeout(() => {
     
    reslove('芬达')
     
    }, 1000)
     
    })
     
    const ret = await promise
     
    console.log(ret)
     
    }
     
    foo2()
     

    上面代码会在1s后打印芬达
    从上面代码来说,await在等一个承诺,好吧,是promise,更严谨的来说,他是在等待一个表达式,只不过这个表达式可以是promise,也可以是其他任意表达式的结果。所以,下面的代码是完全可以运行的:

     
    const foo3 = () => '芬达'
     
    const foo4 = async () => {
     
    const ret = await foo3()
     
    console.log(ret)
     
    }
     
    foo4()

    当然也是打印的芬达

    await 等到了之后会做什么

    通俗点说,你可以把await看做是一个运算符号,如果它等到的不是promise,那么他的运算结果就是当前它所等到的值,如果等到的是promise,那么他会临时阻塞后续代码,直到promise对象resolve,取到 resolve 的值,将其作为运算结果返回。
    emmmmm, 就是这样。

    async 和 promise 的联系

    async 可以看做是promiseGenerator的语法糖,但对其做了改进。

    • 内置执行器,Generator的执行必须依靠执行器,但async的执行器与生俱来,使得async函数与普通函数的调用别无二致。
    • 更好的语义化,就不做解释了吧
    • 返回值是promise,对开发者友好,感觉这个才是最重要的有木有。

    它能为我们做什么?

    这个问题的答案应该是毋庸置疑的,必然是为了解决回调地狱。async/await的优势就在于处理then式调用链呢。 我们可以先假设一个场景: 用户登录之后拿到userId,然后在去调用接口拿到token,最后在其他接口的请求头里添加上token字段.....别问为什么这么白痴的设计,假设而已

    初级前端的写法:

    ajax('login', { username, password }, ({ userId }) => {
     
    ajax('getToken', { userId }, ({ token }) => {
     
    ajax('getOtherInfo', { token }, res => {
     
    // do something...
     
    })
     
    })
     
    })

    是不是头皮发麻,当然,如果你是写这样的代码的人,你可能觉得还可以接受,如果你是维护这样的代码的,你就会明白有多难维护,这里只写了三层,实际中甚至更多。意味着层级嵌套,牵一发而动全身,要改都得改的死胡同,意味着代码缩进都能恶心坏你,所以,初级大圆满前端是如何写的呢?

     
    ajax('login', { username, password })
     
    .then(({ userId }) => ajax('getToken', { userId }))
     
    .then(({ token }) => ajax('getOtherInfo', { token }))
     
    .then(res => {
     
    // do something...
     
    })//这样一来,利用链式then调用,既可以清晰的展现api依赖关系,又可以优雅的缩进代码,但我们是讲async的啊老铁,所以还是看下吧async大法。
    async function foo({ username, password }) {
     
    const { userId } = await ajax('login', { username, password })
     
    const { token } = await ajax('getToken', { userId })
     
    const res = await ajax('getOtherInfo', { token })
     
    // do something ...

    上面有提到,await会临时阻塞后续代码的执行,这就代表着接口调用会按照我们的代码顺序"同步"执行(说是同步,实际上还是异步请求,表现为同步行为是语法糖的原因)。这样,代码就会按照我们的预期执行,接口依赖关系也更加明了,维护起来也是赏心悦目的。

    它的优点和缺陷

    上面有谈到他一部分的优点,其实还有如下优点

    • 语法简洁,使代码可读性更高
    • 能使用try catch捕获异常
    • 使代码更加符合思维逻辑。

    至于缺点呢...

    • 需要babel编译
    • 缺少abort请求中断,缺少异步控制流程。
    • 异常捕获较为麻烦
    • 没有依赖关系的请求需要借助promise.all

    如何优雅的在async/await中处理错误

    理论上来说,await等到的可能是promise.reject,这种情况可以使用try/catch来捕获异常,ok没毛病,像下面这样

    try {
     
    const { userId } = await ajax('login', { username, password })
     
    } catch {
     
    throw new Error('no user found')

    But,如果像上面一样有很多个await呢,怎么办,每次都要写一下嘛,这样岂不是很难受?上周在沸点看到一位大佬对promise的异常处理,很有意义。如图

    , 同样的啊, async也是可以借助 promise来实现统一的异常捕获。

    function util(promise) {
     
    return promise.then(data => [null, data]).catch(err => [err])
     
    }
     
    async function foo({ usernam, password }) {
     
    let userId, token, err
     
    ;[err, { userId }] = await util(ajax('login', { username, password }))
     
    // 因默认数据返回是对象,所以加了解构,部分省略...
     
    // do something
     
    }

    没有大佬的考虑全面,但也足以应付日常需求了

     

    能否将所有函数定义成async?

    现在JS里有async/await了,处理异步代码几乎不再有什么争议,但还是会有人有疑问,为什么不把所有函数都定义成async的,然后所有函数调用都写成await的,这样最终不就可以省略掉所有的async/await关键字了吗(默认隐式async/await)?这样不就达成了“天下无异步”的太平盛世了吗?

    只要稍微动点脑筋就不会有这种想法。

    我们都知道目前的环境下JS它还是一门单线程的语言,然后通过Event Loop来实现异步IO。虽然也有fibjs这种“异类”,会稍微打破一些认知,暂时先不展开,按住不表。基于这个前提,我们就有一些共识,比如:

    • 同一个event里的代码是顺序执行的不可分割的单元,在这里就不需要考虑资源竞争的问题了。
    • 通过callback或者promise方式调用的东西会受到Event Loop的调度,不管它是Macro Task还是Micro Task,反正会进入另一个单元里执行。

    那么有如下代码

    function A() {
      foo()
      bar()
    }
    
    async function B() {
      await foo()
      await bar()
    }

    如果单看这两个函数,如果ABfoobar都没有副作用,那么会觉得这两个函数的效果没什么差别,在这种情况下“默认async/await”似乎是可行的。

    但如果有共享资源和竞争,事情就会变得完全不一样。

    var shared = 0
    
    function A() {
      foo()
      bar()
      return shared++
    }
    
    async function B() {
      await foo()
      await bar()
      return shared++
    }

    如果AB用到了共享资源,对于A而言,因为是完全同步执行的,那么整个A的代码会在一个event里执行,它是“线程安全”的(这里加引号的是因为这个不是严格的线程安全的概念,只是表示个意思),只要通过静态代码分析的手段就可以得知foobarshared有没有副作用,那么这个A函数的执行结果就是可预知的。

    B则完全不一样,因为await关键字会出让执行权,也就是foobarreturn不在一个event里执行,那么在这三行代码的“行缝儿”之间就有无数的可能性,这些缝隙里塞进去一万个event也不得而知。这种情况下对foobar执行静态分析(去判断他们对shared有没有副作用)是没什么卵用的,因为shared被修改的可能性有无数种,比如触发了一个事件导致别的listener修改了shared。也就是说B函数的执行结果是不能通过静态分析而预知的,它不再是纯函数了(废话)。

    这就是async/await的重要性了,它绝对不是一个简单的语言设计的品味问题,不全局省略async/await是因为它明确的告诉写代码的人这个地方会发生什么事情。开发者只要看到它,马上就会对这里的共享资源多提一个心眼,会以完全不一样的眼光去看待B函数。

    而对于严肃地写代码、写严肃的代码而言,“知道一行代码会发生什么”这件事有多重要我想不需要再多强调了。

    本文转载自:

    Jim Liu's Blog

    https://blog.csdn.net/weixin_34212189/article/details/91383653

  • 相关阅读:
    Revit命令之平面区域
    Revit平面视图控制
    电动手摇两用风机Revit族模型
    中田麻吉
    传递项目标准工具
    Sketchup机电专业BIM插件EngeeringToolBox
    机电专业协同模式
    lumion2.5下载及破解安装详细过程
    人防工程空调设计规范
    BIM软件之BIMsight
  • 原文地址:https://www.cnblogs.com/caihongmin/p/15048063.html
Copyright © 2020-2023  润新知