随着 Node 7 的发布,越来越多的人开始研究据说是异步编程终级解决方案的 async/await。我第一次看到这组关键字并不是在 JavaScript 语言里,而是在 c# 5.0 的语法中。C# 的 async/await 需要在 .NET Framework 4.5 以上的版本中使用,因此我还很悲伤了一阵——为了要兼容 XP 系统,我们开发的软件不能使用高于 4.0 版本的 .NET Framework。

我之前在《闲谈异步调用“扁平”化》 中就谈到了这个问题。无论是在 C# 还是 JavaScript 中,async/await 都是非常棒的特性,它们也都是非常甜的语法糖。C# 的 async/await 实现离不开 Task 或 Task 类,而 JavaScript 的 async/await 实现,也离不开 Promise。

现在抛开 C# 和 .NET Framework,专心研究下 JavaScript 的 async/await。

async 和 await 在干什么

任意一个名称都是有意义的,先从字面意思来理解。async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

另外还有一个很有意思的语法规定,await 只能出现在 async 函数中。然后细心的朋友会产生一个疑问,如果 await 只能出现在 async 函数中,那这个 async 函数应该怎么调用?

如果需要通过 await 来调用一个 async 函数,那这个调用的外面必须得再包一个 async 函数,然后……进入死循环,永无出头之日……

如果 async 函数不需要 await 来调用,那 async 到底起个啥作用?

async 起什么作用

这个问题的关键在于,async 函数是怎么处理它的返回值的!

我们当然希望它能直接通过 return 语句返回我们想要的值,但是如果真是这样,似乎就没 await 什么事了。所以,写段代码来试试,看它到底会返回什么:

async function testAsync() {return "hello async";
}const result = testAsync();
console.log(result);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

看到输出就恍然大悟了——输出的是一个 Promise 对象。

c:\var\test> node --harmony_async_await .
Promise { 'hello async' }
  • 1
  • 2

所以,async 函数返回的是一个 Promise 对象。从文档中也可以得到这个信息。async 函数(包含函数语句、函数表达式、Lambda表达式)会返回一个 Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象。

async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,我们当然应该用原来的方式:then() 链来处理这个 Promise 对象,就像这样

testAsync().then(v => {console.log(v);    // 输出 hello async
});
  • 1
  • 2
  • 3

现在回过头来想下,如果 async 函数没有返回值,又该如何?很容易想到,它会返回 Promise.resolve(undefined)。

联想一下 Promise 的特点——无等待,所以在没有 await 的情况下执行 async 函数,它会立即执行,返回一个 Promise 对象,并且,绝不会阻塞后面的语句。这和普通返回 Promise 对象的函数并无二致。

那么下一个关键点就在于 await 关键字了。

await 到底在等啥

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行

function getSomething() {return "something";
}async function testAsync() {return Promise.resolve("hello async");
}async function test() {const v1 = await getSomething();const v2 = await testAsync();console.log(v1, v2);
}test();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

await 等到了要等的,然后呢

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

我们来看一个关于阻塞的例子:

var a = 1 + 1;function takeLongTime() {return new Promise(resolve => {setTimeout(() => {resolve('Hello World');}, 2000);});
}async function test() {const v = await takeLongTime();console.log(a);console.log(v);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这里需要注意的是, await函数其实是会阻塞后面的操作的, 它会等到await后面跟着的那个函数, 返回的promise对象执行了resolve函数之后, 才会允许代码继续往后执行, 因此这里的console.log(a)还有console.log(v)都会在takeLongTime函数返回promise对象, 延时2s后执行resolve方法之后, 才会执行两个console语句, 如果setTimeout延时100s, 这里就会等待100s

async/await 帮我们干了啥

作个简单的比较 
上面已经说明了 async 会将其后的函数(函数表达式或 Lambda)的返回值封装成一个 Promise 对象,而 await 会等待这个 Promise 完成,并将其 resolve 的结果返回出来。

现在举例,用 setTimeout 模拟耗时的异步操作,先来看看不用 async/await 会怎么写

function takeLongTime() {return new Promise(resolve => {setTimeout(() => resolve("long_time_value"), 1000);});
}takeLongTime().then(v => {console.log("got", v);
});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

如果改用 async/await 呢,会是这样

function takeLongTime() {return new Promise(resolve => {setTimeout(() => resolve("long_time_value"), 1000);});
}async function test() {const v = await takeLongTime();console.log(v);
}test();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

眼尖的同学已经发现 takeLongTime() 没有申明为 async。实际上,takeLongTime() 本身就是返回的 Promise 对象,加不加 async 结果都一样,如果没明白,请回过头再去看看上面的“async 起什么作用”。

又一个疑问产生了,这两段代码,两种方式对异步调用的处理(实际就是对 Promise 对象的处理)差别并不明显,甚至使用 async/await 还需要多写一些代码,那它的优势到底在哪?

async/await 的优势在于处理 then 链

单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。

假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。我们仍然用 setTimeout 来模拟异步操作:

/*** 传入参数 n,表示这个函数执行的时间(毫秒)* 执行的结果是 n + 200,这个值将用于下一步骤*/
function takeLongTime(n) {return new Promise(resolve => {setTimeout(() => resolve(n + 200), n);});
}function step1(n) {console.log(`step1 with ${n}`);return takeLongTime(n);
}function step2(n) {console.log(`step2 with ${n}`);return takeLongTime(n);
}function step3(n) {console.log(`step3 with ${n}`);return takeLongTime(n);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

现在用 Promise 方式来实现这三个步骤的处理

function doIt() {console.time("doIt");const time1 = 300;step1(time1).then(time2 => step2(time2)).then(time3 => step3(time3)).then(result => {console.log(`result is ${result}`);console.timeEnd("doIt");});
}doIt();// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。

如果用 async/await 来实现呢,会是这样

async function doIt() {console.time("doIt");const time1 = 300;const time2 = await step1(time1);const time3 = await step2(time2);const result = await step3(time3);console.log(`result is ${result}`);console.timeEnd("doIt");
}doIt();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样

还有更酷的

现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。

function step1(n) {console.log(`step1 with ${n}`);return takeLongTime(n);
}function step2(m, n) {console.log(`step2 with ${m} and ${n}`);return takeLongTime(m + n);
}function step3(k, m, n) {console.log(`step3 with ${k}, ${m} and ${n}`);return takeLongTime(k + m + n);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这回先用 async/await 来写:

async function doIt() {console.time("doIt");const time1 = 300;const time2 = await step1(time1);const time3 = await step2(time1, time2);const result = await step3(time1, time2, time3);console.log(`result is ${result}`);console.timeEnd("doIt");
}doIt();// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 800 = 300 + 500
// step3 with 1800 = 300 + 500 + 1000
// result is 2000
// doIt: 2907.387ms
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

除了觉得执行时间变长了之外,似乎和之前的示例没啥区别啊!别急,认真想想如果把它写成 Promise 方式实现会是什么样子?

function doIt() {console.time("doIt");const time1 = 300;step1(time1).then(time2 => {return step2(time1, time2).then(time3 => [time1, time2, time3]);}).then(times => {const [time1, time2, time3] = times;return step3(time1, time2, time3);}).then(result => {console.log(`result is ${result}`);console.timeEnd("doIt");});
}doIt();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

有没有感觉有点复杂的样子?那一堆参数处理,就是 Promise 方案的死穴—— 参数传递太麻烦了,看着就晕! 
通过使用async来写的话, 代码就会简洁得多, 而且可读性也更强

转载自: 
作者: 边城 
原文地址: https://segmentfault.com/a/1190000007535316#articleHeader1

async await相关推荐

  1. setTimeout、setInterval、promise、async/await的顺序详解(多种情况,非常详细~)

    本文很长,列举的情况很多. 在阅读本文之前,如果您有充足的时间,请新建一个项目与本文一同实践. 每段代码都有对应的解释,但是自己动手尝试印象才会更深哦~ setInterval:表示多久执行一次,需要 ...

  2. C# async await 学习笔记2

    C# async await 学习笔记1(http://www.cnblogs.com/siso/p/3691059.html) 提到了ThreadId是一样的,突然想到在WinForm中,非UI线程 ...

  3. promise 和 async await区别

     什么是Async/Await? async/await是写异步代码的新方式,以前的方法有回调函数和Promise. async/await是基于Promise实现的,它不能用于普通的回调函数. as ...

  4. angular2 学习笔记 ( Rxjs, Promise, Async/Await 的区别 )

    Promise 是 ES 6 Async/Await 是 ES 7 Rxjs 是一个 js 库 在使用 angular 时,你会经常看见这 3 个东西. 它们都和异步编程有关,有些情况下你会觉得用它们 ...

  5. async await:比requests 更强大

    最近公司 Python 后端项目进行重构,整个后端逻辑基本都变更为采用"异步"协程的方式实现.看着满屏幕经过 async await(协程在 Python 中的实现)修饰的代码,我 ...

  6. @async 默认线程池_.NET Web应用中为什么要使用async/await异步编程?

    布莱恩特:.NET Core开发精选文章目录,持续更新,欢迎投稿!​zhuanlan.zhihu.com 前言 1.什么是async/await? await和async是.NET Framework ...

  7. Atitit. Async await 优缺点 异步编程的原理and实现 java c# php

    Atitit. Async await 优缺点 异步编程的原理and实现 java c# php 1. async & await的来源1 2. 异步编程history1 2.1. 线程池 2 ...

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

    2019独角兽企业重金招聘Python工程师标准>>> **摘要:**面试必问 原文:8张图帮你一步步看清 async/await 和 promise 的执行顺序 作者:ziwei3 ...

  9. 用 async/await 来处理异步

    引用出处:https://www.cnblogs.com/SamWeb/p/8417940.html 昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简 ...

  10. 关于C#中async/await中的异常处理(上)

    关于C#中async/await中的异常处理(上) 参考文章: (1)关于C#中async/await中的异常处理(上) (2)https://www.cnblogs.com/sunjie9606/p ...

最新文章

  1. C++标准库中各种排序归纳
  2. 基于Redis实现一个分布式锁
  3. 一个简单的pygame接金币游戏
  4. Angularjs编写KindEditor,UEidtor指令
  5. ORA-07445 ERROR on auto execute of job 8913
  6. java中d怎样转换D,如何将ZonedDateTime转换为date?
  7. 假设检验——抽样调查的结论依赖于样本量的大小
  8. 【Django 2021年最新版教程5】前端传递数据到后端处理 GET 方法
  9. 谭浩强C语言练习题及详细答案
  10. 【毕业设计/课程设计】基于Flutter的聊天社交应用
  11. 51单片机最小系统解读
  12. 金融企业如何构建有效的数据分析体系?1000+案例经验汇总
  13. [计算机视觉] AprilTag 2: Efficient and robust fiducial detection(2016)论文理解
  14. uni-app 中英文切换
  15. 显卡刷bios变砖怎么办--关于矿卡,一些惊呆我的骚操作
  16. html怎么设置一个表格的宽度一样吗,html表格单元格大小 怎样在html中设置所有表格大小一样...
  17. 微型断路器的选择使用
  18. 大数模板——来自jxy师兄
  19. k8s部署springcloud架构的一些心得体会
  20. 计算机专业毕业英文论文一万字,计算机专业毕业论文外文翻译2篇共15页.DOC

热门文章

  1. 单片机楼梯灯制作(C语言版)
  2. ECG:机器学习之预处理算法与R波定位算法(附部分代码)
  3. shell cat命令
  4. powerDesigner ldm导出实体到excel(VBScript)
  5. Python爬虫基础操作二
  6. xsens30显示错误No MTi device found
  7. Retinoic acid PEG Mal,RA-PEG-Maleimide,维甲酸PEG马来酰亚胺,聚乙二醇化视黄酸提供更好的水溶性
  8. 腾讯QQ分组管理小分析——陌生人加入讨论组
  9. 成都拓嘉辰丰:如何利用拼多多客服提升店铺转化
  10. NOIP大纲整理:(十六)反转问题与弹性碰撞