6个Async/Await完胜Promise的原因
友情提醒:NodeJS自从7.6版开始已经内置了对async/await的支持。如果你还没用过该特性,那么接下来我会给出一系列的原因解释为何你应该立即开始使用它并且会结合示例代码说明。
async/await快速入门
为了让还没听说过这个特性的小伙伴们有一个大致了解,以下是一些关于该特性的简要介绍:
- async/await是一种编写异步代码的新方法。在这之前编写异步代码使用的是回调函数和promise。
- async/await实际是建立在promise之上的。因此你不能把它和回调函数搭配使用。
- async/await和promise一样,是非阻塞的。
- async/await可以使异步代码在形式上更接近于同步代码。这就是它最大的价值。
语法
假设有一个getJSON
方法,它返回一个promise,该promise会被resolve为一个JSON对象。我们想要调用该方法,输出得到的JSON对象,最后返回"done"
。
以下是使用promise的实现方式:
const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})
makeRequest()
使用async/await则是这样的:
const makeRequest = async () => {
console.log(await getJSON())
return "done"
}
makeRequest()
使用async/await时有以下几个区别:
- 在定义函数时我们使用了
async
关键字。await
关键字只能在使用async
定义的函数的内部使用。所有async
函数都会返回一个promise,该promise最终resolve的值就是你在函数中return
的内容。 由于第一点中的原因,你不能在顶级作用域中await一个函数。因为顶级作用域不是一个
async
方法。// this will not work in top level
// await makeRequest() // this will work
makeRequest().then((result) => {
// do something
})await getJSON()
意味着直到getJSON()
返回的promise在resolve之后,console.log
才会执行并输出resolove的值。
为何使用async/await编写出来的代码更好呢?
1. 简洁
看看我们节省了多少代码吧。即使是在这么一个简单的例子中,我们也节省了可观的代码。我们不需要为.then
编写一个匿名函数来处理返回结果,也不需要创建一个data
变量来保存我们实际用不到的值。我们还避免了代码嵌套。这些小优点会在真实项目中变得更加明显。
2. 错误处理
async/await终于使得用同一种构造(古老而好用的try/catch
) 处理同步和异步错误成为可能。在下面这段使用promise的代码中,try/catch
不能捕获JSON.parse
抛出的异常,因为该操作是在promise中进行的。要处理JSON.parse
抛出的异常,你需要在promise上调用.catch
并重复一遍异常处理的逻辑。通常在生产环境中异常处理逻辑都远比console.log
要复杂,因此这会导致大量的冗余代码。
const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
现在看看使用了async/await的情况,catch
代码块现在可以捕获JSON.parse
抛出的异常了:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
3. 条件分支
假设有如下逻辑的代码。请求数据,然后根据返回数据中的某些内容决定是直接返回这些数据还是继续请求更多数据:
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
只是阅读这些代码已经够让你头疼的了。一不小心你就会迷失在这些嵌套(6层),空格,返回语句中。
在使用async/await改写后,这段代码的可读性大大提高了:
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
4. 中间值
你可能会遇到这种情况,请求promise1
,使用它的返回值请求promise2
,最后使用这两个promise的值请求promise3
。对应的代码看起来是这样的:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}
如果promise3
没有用到value1
,那么我们就可以把这几个promise改成嵌套的模式。如果你不喜欢这种编码方式,你也可以把value1和value2封装在一个Promsie.all
调用中以避免深层次的嵌套:
const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}
这种方式为了保证可读性而牺牲了语义。除了避免嵌套的promise,没有其它理由要把value1
和value2
放到一个数组里。
同样的逻辑如果换用async/await编写就会非常简单,直观。
const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
5. 异常堆栈
假设有一段串行调用多个promise的代码,在promise串中的某一点抛出了异常:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
从promise串返回的异常堆栈中没有包含关于异常是从哪一个环节抛出的信息。更糟糕的是,它还会误导你,它包含的唯一的函数名是callAPromise
,然而该函数与此异常并无关系。(这种情况下文件名和行号还是有参考价值的)。
然而,在使用了async/await的代码中,异常堆栈指向了正确的函数:
const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})
这带来的好处在本地开发环境中可能并不明显,但当你想要在生产环境的服务器上获取有意义的异常信息时,这会非常有用。在这种情况下,知道异常来自makeRequest
而不是一连串的then
调用会有意义的多。
6. 调试
最后压轴的一点,使用async/await最大的优势在于它很容易被调试。由于以下两个原因,调试promise一直以来都是很痛苦的。
你不能在一个返回表达式的箭头函数中设置断点(因为没有代码块)
如果你在一个
.then
代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then
代码块,因为调试器只能跟踪同步代码的『每一步』。通过使用async/await,你不必再使用箭头函数。你可以对await语句执行步进操作,就好像他们都是普通的同步调用一样。
结论
async/await是过去几年中JavaScript引入的最具革命性的特性之一。它使你意识到promise在语法上的糟糕之处,并提供了一种简单,直接的替代方案。
疑虑
一些你在使用此特性可能出现的疑虑:
- 它使得异步代码不那么明显了:我们的眼睛已经学会了通过寻找回调函数或
.then
来发现异步代码,因此需要一段时间来适应新的标识符。C#中已经内置此特性多年了,熟悉的人都知道这只是一个小小的,暂时的不便。 - Node 7不是一个LTS发布版:是的,但是Node 8将在下个月发布。同时,迁移你的代码到最新版本可能根本不需要任何代价。
6个Async/Await完胜Promise的原因的更多相关文章
- JavaScript 的 Async\/Await 完胜 Promise 的六
参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: A ...
- JavaScript异步编程——Async/Await vs Promise
兼容性 提醒一下各位,Node 现在从版本 7.6 开始就支持 async/await 了.而就在前几天,Node 8已经正式发布了,你可以放心地使用它. 如果你还没有试过它,这里有一堆带有示例的理由 ...
- Async/Await替代Promise的6个理由
译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止 ...
- 8张图让你一步步看清 async/await 和 promise 的执行顺序
摘要: 面试必问 原文:8张图帮你一步步看清 async/await 和 promise 的执行顺序 作者:ziwei3749 Fundebug经授权转载,版权归原作者所有. 为什么写这篇文章? 说实 ...
- 8 张图帮你一步步看清 async/await 和 promise 的执行顺序(转)
https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651555491&idx=1&sn=73779f84c289d9 ...
- [转] Async/Await替代Promise的6个理由
Node.js 7.6已经支持async/await了,如果你还没有试过,这篇博客将告诉你为什么要用它. Async/Await简介 对于从未听说过async/await的朋友,下面是简介: asyn ...
- async/await actor promise 异步编程
Python协程:从yield/send到async/await http://blog.guoyb.com/2016/07/03/python-coroutine/ Async/Await替代Pro ...
- 关于async/await、promise和setTimeout执行顺序
先来一道关于async/await.promise和setTimeout的执行顺序的题目: async function async1() { console.log('async1 start'); ...
- js异步回调Async/Await与Promise区别 新学习使用Async/Await
Promise,我们了解到promise是ES6为解决异步回调而生,避免出现这种回调地狱,那么为何又需要Async/Await呢?你是不是和我一样对Async/Await感兴趣以及想知道如何使用,下面 ...
随机推荐
- SpringBoot笔记十五:任务
目录 异步任务 定时任务 异步任务 注解:@Async,@EnableAsync 我新建一个Service,就叫AsyncService package com.example.service; im ...
- Java中Sax解析XML
SAX基于事件的解析,解析器在一次读取XML文件中根据读取的数据产生相应的事件,由应用程序实现相应的事件处理逻辑,即它是一种“推”的解析方式:这种解析方法速度快.占用内存少,但是它需要应用程序自己处理 ...
- C#设计模式(13)——享元模式
1.享元模式介绍 在软件开发中我们经常遇到多次使用相似或者相同对象的情况,如果每次使用这个对象都去new一个新的实例会很浪费资源.这时候很多人会想到前边介绍过的一个设计模式:原型模式,原型模式通过拷贝 ...
- GCC编译器原理(三)------编译原理三:编译过程(2-2)---编译之语法分析
2.2 语法分析 语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree).整个分析过程采用了上下文无关语法(Context-free G ...
- 十、uboot 代码流程分析---run_main_loop
调用 board_init_r,传入全局 GD 和 SDRAM 中的目的地址 gd->rellocaddr void board_init_r(gd_t *new_gd, ulong dest_ ...
- Log4j2 快速开始
1.配置 默认 Log4j2可以将自己配置为记录错误及更高级别日志,并将消息记录到控制台中. [显示配置]1.检测log4j.configurationFile系统属性,如果属性存在,就从指定文件加载 ...
- MySQL api
今天看去年年中写的代码,留意到一个关键时刻能提高效率的api:on duplicate key update: 语法: INSERT INTO INSERT INTO g_iot_user_build ...
- Redis是可以安装成windows服务-开机自启
其实Redis是可以安装成windows服务的,开机自启动,命令如下 redis-server --service-install redis.windows.conf 安装完之后,就可看到Redis ...
- Linux ISO镜像挂载
挂载本地镜像? 镜像光盘的格式 iso9660 mount -t iso9660 -o,loop /data/centos.iso /iso 查看挂载磁盘 df -h cat /proc/mounts ...
- Empirical Evaluation of Speaker Adaptation on DNN based Acoustic Model
DNN声学模型说话人自适应的经验性评估 年3月27日 发表于:Sound (cs.SD); Computation and Language (cs.CL); Audio and Speech Pro ...