如何避免 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线程和 ...
随机推荐
- ROS(indigo)_turtlebot仿真示例包括stage和gazebo
ROS(indigo)_turtlebot仿真示例包括stage和gazebo 现上参考网址: turtlebot:http://wiki.ros.org/Robots/TurtleBot stage ...
- Android万能适配器Adapter-android学习之旅(74)
万能适配器的代码的github地址是https://github.com/fengsehng/CommonAdapter 万能适配器的代码的github地址是https://github.com/fe ...
- python的w+到底是什么
python 文件处理的打开方式有很多种, os.mknod("test.txt") 创建空文件 fp = open("test.txt",w) 直接打开一个文 ...
- (三十)PickerView文字和随机数的使用
PickerView用于展示供选择的内容(例如日期选取.点菜等). 有三种情况: 1.每一列都是独立的选取 2.右边的列受到左边列的影响 3.包含图片 PickerView和TableView类似,通 ...
- Linux进程状态解析之R、S、D、T、Z、X
文章转载自:http://hi.baidu.com/shining_pc/item/21abcb32a4d2d484c3cf2950 Linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程 ...
- 动态获取html页面的内容,并且取其中的某块元素的方法
$.ajax({ url: "http://192.168.1.59:8888/app-tpl-webapp/tpl/design.html", async:false, ...
- 【Coding算法导论】第4章:最大子数组问题
Coding算法导论 本系列文章主要针对算法导论一书上的算法,将书中的伪代码用C++实现 代码未经过大量数据测试,如有问题,希望能在回复中指出! (一)问题描述 给定一个数组,求数组中连续的子数组的和 ...
- Obj-C中内存的管理一瞥
注意,ARC仅仅(自动)释放你手工管理的Objective-C类实例的内存, 但是不会释放由C函数或者Core Foundation(Cocoa的底层,C语言的变体)申请的内存.
- Shell Scripts - 循环while,until,for
while...do...done until...do...done for...do...done
- C++开发基础
硬件配置: 586以上PC兼容机或品牌机,配有彩色显示器.鼠标.键盘,内存不小于20MB,硬 盘自由空间不少于60MB.推荐配置为内存32MB或64MB(或以上),硬盘自由空间500MB 以上. 软件 ...