随笔-async await的基本用法以及使用陷阱,高效使用技巧,分享PDF高清版

86 阅读7分钟

最后

推荐一些系统学习的途径和方法。

路线图

每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。

HTML 和 CSS:

html5知识

css基础知识

开源分享:docs.qq.com/doc/DSmRnRG…

async no return 相当于 return undefined

 

async throw exception

 

await使用的地方

浏览器环境下,在HTML的script标签中,或js文件中await必须在async函数中使用,否则报错

浏览器控制台中,可以直接使用await,不必再async函数中

Node环境下,必须在async函数中使用await,否则报错

await一般用于修饰一个promise对象,await的返回值是被修饰promise对象的成功结果

await 修饰一个promise对象,相当于调用了promise对象的then方法获得了成功结果,并作为await的返回值

如果await修饰的promise对象状态变为失败,则await会抛出异常,需要try...catch捕获

await也可以用于修饰非promise对象,但是底层会将其封装为promise对象后再获取其成功结果作为await的返回值

相当于await直接将修饰的非promise值,直接作为返回值

await会等待promise对象的成功结果,在promise对象成功结果未返回期间,await会暂停async函数代码向下执行

注意这里虽然await修饰的是一个非promise对象 1,但是底层会将 1 封装为promise对象,然后执行它的then方法,将成功结果 1 返回给await,所以这里await 1 会产生一个微任务,微任务会等待所有同步代码执行完成后执行。

需要注意的是async函数中的console.log(1)被阻塞执行的原因:

async函数的主体实现是(生成器函数generator),调用实现是(执行器函数co)

而generator执行无法像普通函数一样被调用执行,generator调用只会返回一个迭代器对象iterator。

以下是co函数的简要逻辑:

而iterator可以控制generator函数的执行,当iterator.next()调用时,generator函数就会执行函数体,当执行遇到yield(相当于async函数中await)[await 1],执行暂停,并且yield会产出一个值[1](相当于async函数中await修饰的值)作为iterator.next()的返回值,而co函数会拿到这个返回值,将其包装为一个promise对象[Promise.resolve(1)],并调用其then方法获取成功结果[此时产生微任务],等得到成功结果后,将其作为下一次iterator.next(data)的参数data[async函数的后续代码 console.log(1)执行依赖于这里的iterator.next,但是这里的iterator.next又需要等带微任务的成功结果],该data最终会传递给上一次next对应的yield的返回值,然后再次继续执行generator后续代码,直到又遇到yield,重复以上逻辑,或者遇到return,结束generator执行。

使用陷阱


对于可以并行的异步任务错误使用await,导致执行效率下降

我们直到async await可以优雅地实现异步任务间串行,但是我们需要知道异步任务串行地场景,一般是后一个异步任务依赖于上一个异步任务地结果,此时才需要串行异步任务。

但是多个互相无依赖关系地异步任务是不需要串行的,强行使用串行反而会降低执行效率 

可以发现a,b,c三个读取文件的无依赖的异步任务,每一个异步任务读取大约2s多一点,这里由于使用了await串行,所以导致a读取完毕后,才能读取b,b读取完毕后才能读取c,将效率降低了3倍数。

那么await如何实现并行呢?

怎么说呢,其实await无法实现并行,每个await都会开启一个微任务,并阻塞async函数中后续代码执行

那么在异步任务并行场景下,就不得不少用await了吗?

这种写法为什么也能实现await并行呢?

我们知道 readFilePromisify('a.txt')会返回一个promise对象,且在返回promise对象的同时就已经开启了异步任务执行,

对比下面这种方式

const a = await readFilePromisify('a.txt')

const b = await readFilePromisify('b.txt')

const c = await readFilePromisify('c.txt')

由于await会阻塞后续代码执行,所以当在读取a.txt的过程中,后面的b,c的readFilePromisify是没有被调用的,也就无法开启异步任务

forEach的回调函数中使用await等待异步操作完成,实际forEach不会暂停遍历操作

我们来重写一些Array.prototype.forEach 

可能还是不够明白,我们将arr.forEach和Array.prototype.forEach合并

我们知道await只能阻塞async函数向下执行,并且await会开启一个微任务,当开启微任务后,JS引擎线程就会将微任务加入异步任务队列,然后继续执行同步代码,即新一轮的for循环

所以需要注意:await只会阻塞async函数内部代码执行,而不会阻塞async函数外部同步代码的执行

那么如果我们想要让遍历等待遍历中的异步任务完成后才继续下一次遍历该如何实现呢?

 测试发现上面,for...in,for..of循环都可以

那么是否意味着forEach,只要也在外面包装一个async函数壳子就行了呢?

最后

好了,这就是整理的前端从入门到放弃的学习笔记,还有很多没有整理到,我也算是边学边去整理,后续还会慢慢完善,这些相信够你学一阵子了。

做程序员,做前端工程师,真的是一个学习就会有回报的职业,不看出身高低,不看学历强弱,只要你的技术达到应有的水准,就能够得到对应的回报。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

学习从来没有一蹴而就,都是持之以恒的,正所谓活到老学到老,真正懂得学习的人,才不会被这个时代的洪流所淘汰。