友情提醒: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的实现方式:

  1. const makeRequest = () =>
  2. getJSON()
  3. .then(data => {
  4. console.log(data)
  5. return "done"
  6. })
  7. makeRequest()

使用async/await则是这样的:

  1. const makeRequest = async () => {
  2. console.log(await getJSON())
  3. return "done"
  4. }
  5. makeRequest()

使用async/await时有以下几个区别:

  1. 在定义函数时我们使用了async关键字。await关键字只能在使用async定义的函数的内部使用。所有async函数都会返回一个promise,该promise最终resolve的值就是你在函数中return的内容。
  2. 由于第一点中的原因,你不能在顶级作用域中await一个函数。因为顶级作用域不是一个async方法。

    1. // this will not work in top level
    2. // await makeRequest()
    3. // this will work
    4. makeRequest().then((result) => {
    5. // do something
    6. })
  3. 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要复杂,因此这会导致大量的冗余代码。

  1. const makeRequest = () => {
  2. try {
  3. getJSON()
  4. .then(result => {
  5. // this parse may fail
  6. const data = JSON.parse(result)
  7. console.log(data)
  8. })
  9. // uncomment this block to handle asynchronous errors
  10. // .catch((err) => {
  11. // console.log(err)
  12. // })
  13. } catch (err) {
  14. console.log(err)
  15. }
  16. }

现在看看使用了async/await的情况,catch代码块现在可以捕获JSON.parse抛出的异常了:

  1. const makeRequest = async () => {
  2. try {
  3. // this parse may fail
  4. const data = JSON.parse(await getJSON())
  5. console.log(data)
  6. } catch (err) {
  7. console.log(err)
  8. }
  9. }

3. 条件分支

假设有如下逻辑的代码。请求数据,然后根据返回数据中的某些内容决定是直接返回这些数据还是继续请求更多数据:

  1. const makeRequest = () => {
  2. return getJSON()
  3. .then(data => {
  4. if (data.needsAnotherRequest) {
  5. return makeAnotherRequest(data)
  6. .then(moreData => {
  7. console.log(moreData)
  8. return moreData
  9. })
  10. } else {
  11. console.log(data)
  12. return data
  13. }
  14. })
  15. }

只是阅读这些代码已经够让你头疼的了。一不小心你就会迷失在这些嵌套(6层),空格,返回语句中。

在使用async/await改写后,这段代码的可读性大大提高了:

  1. const makeRequest = async () => {
  2. const data = await getJSON()
  3. if (data.needsAnotherRequest) {
  4. const moreData = await makeAnotherRequest(data);
  5. console.log(moreData)
  6. return moreData
  7. } else {
  8. console.log(data)
  9. return data
  10. }
  11. }

4. 中间值

你可能会遇到这种情况,请求promise1,使用它的返回值请求promise2,最后使用这两个promise的值请求promise3。对应的代码看起来是这样的:

  1. const makeRequest = () => {
  2. return promise1()
  3. .then(value1 => {
  4. // do something
  5. return promise2(value1)
  6. .then(value2 => {
  7. // do something
  8. return promise3(value1, value2)
  9. })
  10. })
  11. }

如果promise3没有用到value1,那么我们就可以把这几个promise改成嵌套的模式。如果你不喜欢这种编码方式,你也可以把value1和value2封装在一个Promsie.all调用中以避免深层次的嵌套:

  1. const makeRequest = () => {
  2. return promise1()
  3. .then(value1 => {
  4. // do something
  5. return Promise.all([value1, promise2(value1)])
  6. })
  7. .then(([value1, value2]) => {
  8. // do something
  9. return promise3(value1, value2)
  10. })
  11. }

这种方式为了保证可读性而牺牲了语义。除了避免嵌套的promise,没有其它理由要把value1value2放到一个数组里。

同样的逻辑如果换用async/await编写就会非常简单,直观。

  1. const makeRequest = async () => {
  2. const value1 = await promise1()
  3. const value2 = await promise2(value1)
  4. return promise3(value1, value2)
  5. }

5. 异常堆栈

假设有一段串行调用多个promise的代码,在promise串中的某一点抛出了异常:

  1. const makeRequest = () => {
  2. return callAPromise()
  3. .then(() => callAPromise())
  4. .then(() => callAPromise())
  5. .then(() => callAPromise())
  6. .then(() => callAPromise())
  7. .then(() => {
  8. throw new Error("oops");
  9. })
  10. }
  11. makeRequest()
  12. .catch(err => {
  13. console.log(err);
  14. // output
  15. // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
  16. })

从promise串返回的异常堆栈中没有包含关于异常是从哪一个环节抛出的信息。更糟糕的是,它还会误导你,它包含的唯一的函数名是callAPromise,然而该函数与此异常并无关系。(这种情况下文件名和行号还是有参考价值的)。

然而,在使用了async/await的代码中,异常堆栈指向了正确的函数:

  1. const makeRequest = async () => {
  2. await callAPromise()
  3. await callAPromise()
  4. await callAPromise()
  5. await callAPromise()
  6. await callAPromise()
  7. throw new Error("oops");
  8. }
  9. makeRequest()
  10. .catch(err => {
  11. console.log(err);
  12. // output
  13. // Error: oops at makeRequest (index.js:7:9)
  14. })

这带来的好处在本地开发环境中可能并不明显,但当你想要在生产环境的服务器上获取有意义的异常信息时,这会非常有用。在这种情况下,知道异常来自makeRequest而不是一连串的then调用会有意义的多。

6. 调试

最后压轴的一点,使用async/await最大的优势在于它很容易被调试。由于以下两个原因,调试promise一直以来都是很痛苦的。

  1. 你不能在一个返回表达式的箭头函数中设置断点(因为没有代码块)

  2. 如果你在一个.then代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的『每一步』。

    通过使用async/await,你不必再使用箭头函数。你可以对await语句执行步进操作,就好像他们都是普通的同步调用一样。

结论

async/await是过去几年中JavaScript引入的最具革命性的特性之一。它使你意识到promise在语法上的糟糕之处,并提供了一种简单,直接的替代方案。

疑虑

一些你在使用此特性可能出现的疑虑:

  • 它使得异步代码不那么明显了:我们的眼睛已经学会了通过寻找回调函数或.then来发现异步代码,因此需要一段时间来适应新的标识符。C#中已经内置此特性多年了,熟悉的人都知道这只是一个小小的,暂时的不便。
  • Node 7不是一个LTS发布版:是的,但是Node 8将在下个月发布。同时,迁移你的代码到最新版本可能根本不需要任何代价。

6个Async/Await完胜Promise的原因的更多相关文章

  1. JavaScript 的 Async\/Await 完胜 Promise 的六

    参考:http://www.10tiao.com/html/558/201705/2650964601/1.html Node 现在从版本 7.6 开始就支持 async/await 了. 简介: A ...

  2. JavaScript异步编程——Async/Await vs Promise

    兼容性 提醒一下各位,Node 现在从版本 7.6 开始就支持 async/await 了.而就在前几天,Node 8已经正式发布了,你可以放心地使用它. 如果你还没有试过它,这里有一堆带有示例的理由 ...

  3. Async/Await替代Promise的6个理由

    译者按: Node.js的异步编程方式有效提高了应用性能:然而回调地狱却让人望而生畏,Promise让我们告别回调函数,写出更优雅的异步代码:在实践过程中,却发现Promise并不完美:技术进步是无止 ...

  4. 8张图让你一步步看清 async/await 和 promise 的执行顺序

    摘要: 面试必问 原文:8张图帮你一步步看清 async/await 和 promise 的执行顺序 作者:ziwei3749 Fundebug经授权转载,版权归原作者所有. 为什么写这篇文章? 说实 ...

  5. 8 张图帮你一步步看清 async/await 和 promise 的执行顺序(转)

    https://mp.weixin.qq.com/s?__biz=MzAxODE2MjM1MA==&mid=2651555491&idx=1&sn=73779f84c289d9 ...

  6. [转] Async/Await替代Promise的6个理由

    Node.js 7.6已经支持async/await了,如果你还没有试过,这篇博客将告诉你为什么要用它. Async/Await简介 对于从未听说过async/await的朋友,下面是简介: asyn ...

  7. async/await actor promise 异步编程

    Python协程:从yield/send到async/await http://blog.guoyb.com/2016/07/03/python-coroutine/ Async/Await替代Pro ...

  8. 关于async/await、promise和setTimeout执行顺序

    先来一道关于async/await.promise和setTimeout的执行顺序的题目: async function async1() { console.log('async1 start'); ...

  9. js异步回调Async/Await与Promise区别 新学习使用Async/Await

    Promise,我们了解到promise是ES6为解决异步回调而生,避免出现这种回调地狱,那么为何又需要Async/Await呢?你是不是和我一样对Async/Await感兴趣以及想知道如何使用,下面 ...

随机推荐

  1. VMware 无法打开内核设备 \\.\Global\vmx86

    无法打开内核设备 \\.\Global\vmx86: 系统找不到指定的文件.你想要在安装 VMware Workstation 前重启吗? vmware 安装完成后,打开现有虚拟系统时,报错. 无法打 ...

  2. VScode 1.13 gocode提示dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected..

    在将VScode升级至 1.13后让升级gocode,在升级时报出如下错误 D:\go_work\src>go get -u -v github.com/mdempsky/gocode gith ...

  3. ELASTICSEARCH 搜索的评分机制

    从我们在elasticsearch复合框输入搜索语句到结果显示,展现给我们的是一个按score得分从高到底排好序的结果集.下面就来学习下elasticsearch怎样计算得分. Lucene(或 El ...

  4. js方法参数中含有单引号双引号的处理

    最近在做项目时,遇到一个问题.当在js脚本中,拼接生成一个tr,然后添加到一个表格里. //假定testval是从后台传过来的数据 var testval = "含有'半角单引号的字符串&q ...

  5. Mac 下搭建环境 homebrew/git/node.js/npm/vsCode...

    主要记录一下 homebrew/git/node.js/npm/mysql 的命令行安装 1. 首先安装 homebrew  也是一个包管理工具: mac 里打开终端命令行工具,粘下面一行回车安装br ...

  6. Jrebel for Android 安装使用

    1.打开File-Setting-plugin-browse repositories.然后点击Manger repositories添加我们的私人存储库 http://dl.zeroturnarou ...

  7. JS判断是电脑浏览器还是手机端浏览器,并根据不同的终端跳转到不同的网址

    <!DOCTYPE html> <html> <script> function browserRedirect() { var sUserAgent = navi ...

  8. Entity Framework 6.0 常见异常及解决办法

    Ø  简介 本文主要记录 EF(Entity Framework) 在平时的开发中可能遇到的异常,以及应该如何去解决. 1.   System.InvalidOperationException 1) ...

  9. 【python小练】0013

    第 0013 题: 用 Python 写一个爬图片的程序,爬 这个链接里的日本妹子图片 :-) 科科...妹子就算了,大晚上的爬点吃的吧.食物图集:抿一口,舔一舔,扭一扭~·SCD 写个简单的爬图爬虫 ...

  10. nginx 相关命令

    验证配置是否正确: nginx -t 查看Nginx的版本号:nginx -V 启动Nginx:start nginx 快速停止或关闭Nginx:nginx -s stop 正常停止或关闭Nginx: ...