如何避免 await/async 地狱
原文地址:How to escape async/await hell
译文出自:夜色镇歌的个人博客
async/await 把我们从回调地狱中解救了出来,但是如果滥用就会掉进 async/await 地狱。
本文中我会解释一下什么是 async/await 地狱,并会分享几个技巧去避免。
啥是 await/async 地狱
异步 Javascript 编程中,我们通常会写许多 async 方法,并且使用 await
关键字去等待它,有很多时候下一行的执行并不依赖于上一行,但是我们仍然使用了 await
去等待,所以可能会导致一些性能问题。
一个 await/async 地狱的例子
如何编写一个订购披萨和饮料的代码?它可能会像这样:
(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
})()
看起来没什么问题,也能正常工作,但这并不是一个好的实现。先来看下这段代码都做了什么,以便定位问题。
解释下
我们把代码用 async IIFE
包裹了起来,然后下面这些会依次执行。
- 获取披萨菜单
- 获取饮料菜单
- 从披萨菜单中选择披萨
- 从饮料菜单中选择饮料
- 把选好的披萨加到购物车
- 把选好的饮料加到购物车
- 下单
哪里错了?
正如我刚强调的,这些语句会依次执行,没有并发。仔细想一下,为啥我获取饮料菜单之前得先获取披萨菜单?这两份菜单我应该同时去获取。当然,选择披萨之前得先获取披萨菜单,这个规则同样适用于饮料。
所以我们可以得出结论,披萨相关的工作和饮料相关的工作可以并行进行,但涉及披萨相关工作的各个步骤需要按顺序进行(一步接着一步)。
另一个糟糕的例子
这段代码会获取购物车中的购物项并且发出订购请求。
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,async 函数会执行并且返回一个 Promise,你可以稍后再去resolve。
(async () => {
const value = doSomeAsyncTask()
console.log(value) // an unresolved promise
})()
另一个后果是编译器不知道你想把函数完全执行,所以编译器会退出程序而不完成异步函数,所以还是需要使用 await 关键字
promises 一个有趣的特性就是你可以在一行代码中去得到 Promise ,而在另外一行中去等待并 resolve,这是避免 async/await 地狱的关键之处。
(async () => {
const promise = doSomeAsyncTask()
const value = await promise
console.log(value) // the actual value
})()
正如你看到的,doSomeAsyncTask()
方法返回一个 Promise,调用的时候它已经开始执行了,为了得到他的解析值,我们使用了 await 关键字,告诉编译器等待解析完毕再执行下一行。
如何避免 async/await 地狱
你应该按照这些步骤来避免 async/await 地狱:
找到语句的依赖关系
第一个例子中,我们选择了一个披萨和一杯饮料。总结一下,选择披萨之前得先获取披萨菜单,加到购物车之前得先选好,这三个步骤都是相互依赖的,必须等待上一个步骤完成后才能进行下一步。
我们选择饮料的时候并不依赖于选择披萨,所以选择披萨和饮料是可以并行执行的。这也是机器能比我们做的更好的一件事。
封装相互依赖的异步方法
正如你看到的,选择披萨的依赖有获取披萨菜单、选择、添加到购物车。所以我们把这些依赖放在一个异步方法里,饮料同理,这也是为什么我们会有 selectPizza()
和 selectDrink()
两个异步方法。
并行执行
我们利用事件循环去非阻塞并行地执行这些异步方法,通常会用的两个方法就是尽早的返回 Promise
和使用 Promise.all()
我们修复一下代码,把这三个方法应用到我们的例子中去。
修改下代码
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
。处理这种情况很简单,我们先把 Promises 放进数组,然后使用 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
}
// Although I prefer it this way
async function orderItems() {
const items = await getCartItems() // async call
const promises = items.map((item) => sendRequest(item))
await Promise.all(promises) // async call
}
希望本文可以引发你对 async/await 使用的思考,也希望能帮助你提升程序的性能。
如何避免 await/async 地狱的更多相关文章
- 使用Typescript写的Vue初学者Hello World实例(实现按需加载、跨域调试、await/async)
万事开头难,一个好的Hello World程序可以节省我们好多的学习时间,帮助我们快速入门.Hello World程序之所以是入门必读必会,就是因为其代码量少,简单易懂.但我觉得,还应该做到功能丰富, ...
- Vue + WebPack + Typescript初学者VSCode项目 (按需加载、跨域调试、await/async)
万事开头难,一个好的Hello World程序可以节省我们好多的学习时间,帮助我们快速入门.Hello World程序之所以是入门必读必会,就是因为其代码量少,简单易懂.但我觉得,还应该做到功能丰富, ...
- javascript异步编程的前世今生,从onclick到await/async
javascript与异步编程 为了避免资源管理等复杂性的问题, javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是为 ...
- Silverlight项目笔记1:UI控件与布局、MVVM、数据绑定、await/async、Linq查询、WCF RIA Services、序列化、委托与事件
最近从技术支持转到开发岗,做Silverlight部分的开发,用的Prism+MVVM,框架由同事搭好,目前做的主要是功能实现,用到了一些东西,侧重于如何使用,总结如下 1.UI控件与布局 常用的主要 ...
- 5分种让你了解javascript异步编程的前世今生,从onclick到await/async
javascript与异步编程 为了避免资源管理等复杂性的问题,javascript被设计为单线程的语言,即使有了html5 worker,也不能直接访问dom. javascript 设计之初是 ...
- es7 await/async解决异步问题
最近做项目遇到一个问题,前端调用ie浏览器中的ocx的方法去查询数据,查询完之后ocx给一个返回值,然后js将返回值当参数传入到另外的函数中去做数据处理,但是遇到一个问题是前端需要异步去执行这个过程 ...
- promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解
* promise承诺 * 解决js中异步编程的问题 * * 异步-同步 * 阻塞-无阻塞 * * 同步和异步的区别? 异步;同步 指的是被请求者 解析:被请求者(该事情的处理者)在处理完事情的时候的 ...
- 【.NET异步编程系列1】:await&async语法糖让异步编程如鱼得水
前导 Asynchronous programming Model(APM)异步编程模型以BeginMethod(...) 和 EndMethod(...)结对出现. IAsyncResult Beg ...
- C#.NET使用Task,await,async,异步执行控件耗时事件(event),不阻塞UI线程和不跨线程执行UI更新,以及其他方式比较
使用Task,await,async,异步执行事件(event),不阻塞UI线程和不跨线程执行UI更新 使用Task,await,async 的异步模式 去执行事件(event) 解决不阻塞UI线程和 ...
随机推荐
- (NO.00001)iOS游戏SpeedBoy Lite成形记(十七)
因为现在游戏内容原来越多了,里面需要存储的数据也多了起来,所以一个较好的办法是将所有的比赛数据存到同一个地方便于存取. 我们需要新建一个游戏数据类,该类贯穿所有需要的场景,存放一切比赛需要的数据.从这 ...
- Dynamics CRM 2011/2013 通过Javascript给lookup字段赋值
仅仅做下记录,因为老是用到但老是忘记 var value = new Array(); value[0] = new Object(); value[0].id = idValue; value[0] ...
- 查看Linux系统的平均负载
1.Linux系统的平均负载的概念 有时候我们会觉得系统响应很慢,但是又找不到原因,这时就要查看平均负载了,看它是否有大量的进程在排队等待.特定时间间隔内运行队列中的平均进程数可以反映系统的繁忙程度, ...
- Linux下MySQL备份以及crontab定时备份
1. 备份某个数据库 ################################################################## # 备份某个数据库 ############ ...
- FPGrowth
在挖掘关联规则的过程中,无可避免要处理海量的数据,也就是事务数据库如此之大,如果采用Apriori算法来挖掘,每次生成频繁k-项集的时候,可能都需要扫描事务数据库一遍,这是非常耗时的操作.那么,可以想 ...
- javase--day_01
一.关键字: /* 关键字: 被java语言复与特定含义的单词. 特点: 组成关键字单词的字母全部小写. 注意: A:goto和c ...
- ERP-非财务人员的财务培训教(二)------如何评价公司/部门经营业绩
一.财务比率分析 第一节 流动性比率 第二节 经营比率 第三节 资本结构比率 第四节 获利能力比率 第五节 现金流量比率 第六节 获现能力比率 二.财务比率金字塔 第二部分 如何评价公 ...
- Paxos 算法
1 概述 Paxos算法是莱斯利·兰伯特(Leslie Lamport,就是 LaTeX 中的"La",此人现在在微软研究院)于1990年提出的一种基于消息传递的一致性算法.[1] ...
- iOS视频播放Demo VideoPlayerDemo
VideoPlayerDemo https://github.com/CaliosD/VideoPlayerDemo 一个视频播放的例子,使用了pod.
- html的meta标签
meta是一个空元素,没有结束标签:meta元素可以附带8个属性,其中4个是通用属性-–dir,lang,xml:lang和title,其他4个是meta特有的属性: schema,name,cont ...