async/await地狱该如何避免详解


Posted in Javascript onMay 10, 2018

前言

async/await是什么

async/await可以说是co模块和生成器函数的语法糖。用更加清晰的语义解决js异步代码。

熟悉co模块的同学应该都知道,co模块是TJ大神写的一个使用生成器函数来解决异步流程的模块,可以看做是生成器函数的执行器。而async/await则是对co模块的升级,内置生成器函数的执行器,不再依赖co模块。同时,async返回的是Promise。

从上面来看,不管是co模块还是async/await,都是将Promise作为最基础的单元,对Promise不很了解的同学可以先深入了解一下Promise。

async/await 写着很爽,不过要注意这些问题。

async/await 让我们摆脱了回调地狱,但是这又引入了 async/await 地狱的问题。

async/await地狱该如何避免详解

什么是 async/await 地狱

在 Javascript 中进行异步编程的时候,人们总是使用很多 await 语句,很多时候我们的语句并不需要依赖于之前的语句,这样就会导致性能问题。

async/await 地狱的例子

我们试着写一个购买披萨和饮料的程序:

(async () => {
 const pizzaData = await getPizzaData() // async call
 const drinkData = await getDrinkData() // async call
 const chosenPizza = choosePizza() // sync call
 const chosenDrink = chooseDrink() // sync call
 await addPizzaToCart(chosenPizza) // async call
 await addDrinkToCart(chosenDrink) // async call
 orderItems() // async call
})()

这段代码运行没有问题。但是不是一个好的实现,因为这增加了不必要的等待。

说明

我们已经将我们的代码封装在异步 IIFE 中,按照下面的顺序执行:

得到披萨名单
获取饮料列表
从列表中选择一个披萨
从列表中选择一种饮料
将选中的披萨加入购物车
将选择的饮品加入购物车
订购购物车中的物品

问题

这里有个问题为什么从列表中选择披萨这个动作要等待获取饮料列表?这两个是没什么关联的操作。其中的关联操作有两组:

获取披萨列表 -》 选择披萨 -》 选择披萨加入购物车

获取饮料列表 -》 选择饮料 -》 选择饮料加入购物车

这两组操作应该是并发执行的。

再来看一个更差的例子

这个 Javascript 代码片段将购物车中的商品并发出订购请求。

async function orderItems() {
 const items = await getCartItems() // async call
 const noOfItems = items.length
 for(var i = 0; i < noOfItems; i++) {
 await sendRequest(items[i]) // async call
 }
}

这种情况 for 循环必须等待 sendRequest() 函数完成才能继续下一次迭代。但是,我们并不需要等待。我们希望尽快发送所有请求。然后我们可以等待所有请求完成。

现在你应该已经对 async/await 地狱有跟多的了解,现在我们再来考虑一个问题

如果我们忘记 await 关键字会怎么样?

如果在调用异步函数忘记使用 await,这意味着执行该功能不需要等待。异步函数将直接返回一个 promise,你可以稍后使用。

(async () => {
 const value = doSomeAsyncTask()
 console.log(value) // an unresolved promise
})()

或者是程序不清楚你想要等待函数执行完,直接退出不会完成这个异步任务。所以我们需要使用 await 这个关键字。

promise 有一个有趣的属性,你可以在某行代码中获取 promise,然后在其他地方中等待它 resolve,这是解决 async/await 地狱的关键。

(async () => {
 const promise = doSomeAsyncTask()
 const value = await promise
 console.log(value) // the actual value
})()

如你所见 doSomeAsyncTask 直接返回一个 Promise 同时这个异步函数 doSomeAsyncTask 已经开始执行,为了得到 doSomeAsyncTask 的返回值,我们需要 await 来告诉

应该如何避免 async/await 地狱

首先我们需要知道哪些命名是有前后依赖关系的。

然后将有依赖关系的系列操作进行分组合并成一个异步操作。

同时执行这些异步函数。

我们来重写这写例子:

async function selectPizza() {
 const pizzaData = await getPizzaData() // async call
 const chosenPizza = choosePizza() // sync call
 await addPizzaToCart(chosenPizza) // async call
}

async function selectDrink() {
 const drinkData = await getDrinkData() // async call
 const chosenDrink = chooseDrink() // sync call
 await addDrinkToCart(chosenDrink) // async call
}

(async () => {
 const pizzaPromise = selectPizza()
 const drinkPromise = selectDrink()
 await pizzaPromise
 await drinkPromise
 orderItems() // async call
})()

// Although I prefer it this way

(async () => {
 Promise.all([selectPizza(), selectDrink()].then(orderItems) // async call
})()

我们将语句分成两个函数。在函数内部,每个语句都依赖于前一个语句的执行。然后我们同时执行这两个函数 selectPizza()和selectDrink() 。

在第二个例子中我们需要处理未知数量的 Promise。处理这个问题非常简单,我们只需要创建一个数组将所有 Promise 存入其中,使用 Promise.all() 方法并行执行:

async function orderItems() {
 const items = await getCartItems() // async call
 const noOfItems = items.length
 const promises = []
 for(var i = 0; i < noOfItems; i++) {
 const orderPromise = sendRequest(items[i]) // async call
 promises.push(orderPromise) // sync call
 }
 await Promise.all(promises) // async call
}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
javascript当onmousedown、onmouseup、onclick同时应用于同一个标签节点Element
Jan 05 Javascript
javascript学习笔记(十七) 检测浏览器插件代码
Jun 20 Javascript
js点击button按钮跳转到另一个新页面
Oct 10 Javascript
深入浅析JavaScript中对事件的三种监听方式
Sep 29 Javascript
举例讲解JavaScript中关于对象操作的相关知识
Nov 16 Javascript
AngularJS中实现用户访问的身份认证和表单验证功能
Apr 21 Javascript
一系列Bootstrap导航条使用方法分享
Apr 29 Javascript
javascript实现粘贴qq截图功能(clipboardData)
May 29 Javascript
vue2.0开发入门笔记之.vue文件的生成和使用
Sep 19 Javascript
微信小程序实现多选功能
Nov 04 Javascript
javascript中floor使用方法总结
Feb 02 Javascript
js实现弹幕墙效果
Dec 10 Javascript
Angular4.x通过路由守卫进行路由重定向实现根据条件跳转到相应的页面(推荐)
May 10 #Javascript
JS中的JSON对象的定义和取值实现代码
May 09 #Javascript
js循环map 获取所有的key和value的实现代码(json)
May 09 #Javascript
js合并两个数组生成合并后的key:value数组
May 09 #Javascript
详解Puppeteer 入门教程
May 09 #Javascript
node基于puppeteer模拟登录抓取页面的实现
May 09 #Javascript
nuxt框架中路由鉴权之Koa和Session的用法
May 09 #Javascript
You might like
CodeIgniter配置之SESSION用法实例分析
2016/01/19 PHP
ThinkPHP整合datatables实现服务端分页的示例代码
2018/02/10 PHP
php输出反斜杠的实例方法
2019/09/19 PHP
解javascript 混淆加密收藏
2009/01/16 Javascript
判断滚动条到底部的JS代码
2013/11/04 Javascript
详解JavaScript中jQuery和Ajax以及JSONP的联合使用
2015/08/13 Javascript
用JavaScript判断CSS浏览器类型前缀的两种方法
2015/10/08 Javascript
Javascript实现的SHA-256加密算法完整实例
2016/02/02 Javascript
JS表格组件神器bootstrap table详解(强化版)
2016/05/26 Javascript
js中利用cookie实现记住密码功能
2020/08/20 Javascript
微信小程序 vidao实现视频播放和弹幕的功能
2016/11/02 Javascript
js获取地址栏参数的两种方法
2017/06/27 Javascript
AngularJS实现select的ng-options功能示例
2017/07/12 Javascript
JS跳转手机站url的若干注意事项
2017/10/18 Javascript
JS实现带动画的回到顶部效果
2017/12/28 Javascript
vue单页开发父子组件传值思路详解
2018/05/18 Javascript
vue 解决循环引用组件报错的问题
2018/09/06 Javascript
vue微信分享出来的链接点开是首页问题的解决方法
2018/11/28 Javascript
手写Vue2.0 数据劫持的示例
2021/03/04 Vue.js
[05:02]2014DOTA2 TI中国区预选赛精彩TOPPLAY第三弹
2014/06/25 DOTA
python实现发送邮件功能代码
2017/12/14 Python
python实现随机调用一个浏览器打开网页
2018/04/21 Python
Python3实现腾讯云OCR识别
2018/11/27 Python
对Django项目中的ORM映射与模糊查询的使用详解
2019/07/18 Python
Python使用lambda表达式对字典排序操作示例
2019/07/25 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
2020/06/23 Python
Python内置函数及功能简介汇总
2020/10/13 Python
Python jieba库分词模式实例用法
2021/01/13 Python
乐高积木玩具美国官网:LEGO Shop US
2016/09/16 全球购物
美国林业供应商:Forestry Suppliers
2019/05/01 全球购物
美国在线鞋类零售商:LifeStride
2019/06/09 全球购物
Vita Fede官网:在意大利手工制作,在纽约市设计
2019/10/25 全球购物
波兰快递服务:Globkurier.pl
2019/11/08 全球购物
优秀医生事迹材料
2014/02/12 职场文书
好的促销活动方案
2014/08/21 职场文书
大学生社会实践活动总结报告
2015/05/06 职场文书