关灯
开启左侧

细说 async/await 相较于 Promise 的优势

[复制链接]
老蚊子 发表于 2019-1-15 13:00:10 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
 
async 函数是什么?
谈及异步回调函数的嵌套,总会让人感到烦躁,特别是当业务逻辑复杂,往往需要调用几次 ajax 才能拿到所有需要的数据。
从最早的回调函数,到 Promise 对象,再到 Generator 函数,每次都有所改进,但又让人觉得不彻底。它们都有额外的复杂性,都需要理解抽象的底层运行机制。所以,我们需要一种方法,更优雅地解决异步操作。于是,async函数出现了。
一句话解释:async 函数,就是 Generator 函数的语法糖。

它有以下几个特点:
  • 建立在 promise 之上。所以,不能把它和回调函数搭配使用。但它会声明一个异步函数,并隐式地返回一个Promise。因此可以直接return变量,无需使用 Promise.resolve 进行转换。
  • 和 promise 一样,是非阻塞的。但不用写 then 及其回调函数,这减少代码行数,也避免了代码嵌套。而且,所有异步调用,可以写在同一个代码块中,无需定义多余的中间变量。
  • 它的最大价值在于,可以使异步代码,在形式上,更接近于同步代码。
  • 它总是与 await 一起使用的。并且,await 只能在 async 函数体内。
  • await 是个运算符,用于组成表达式,它会阻塞后面的代码。如果等到的是 Promise 对象,则得到其 resolve 值。否则,会得到一个表达式的运算结果。

为何说 async 函数是语法糖
async 函数的实现,其实就是将 Generator 函数和自动执行器,包装在一个函数里。下面的这个例子,来自阮老师的 《async 函数的含义和用法》 一文。
  1. async function fn(args) {
  2.     // ...
  3. }

  4. // 等同于
  5. function fn(args) {
  6.     return spawn(function*() {
  7.         // ...
  8.     });
  9. }

  10. // spawn 函数就是自动执行器
  11. function spawn(genF) {
  12.     return new Promise(function(resolve, reject) {
  13.         var gen = genF();
  14.         function step(nextF) {
  15.             try {
  16.                 var next = nextF();
  17.             } catch(e) {
  18.                 return reject(e);
  19.         }
  20.         if(next.done) {
  21.             return resolve(next.value);
  22.         }
  23.         Promise.resolve(next.value).then(function(v) {
  24.             step(function() { return gen.next(v); });      
  25.         }, function(e) {
  26.             step(function() { return gen.throw(e); });
  27.         });
  28.     }
  29.     step(function() { return gen.next(undefined); });
  30.   });
  31. }
复制代码

所以说,async 函数是 Generator 函数的语法糖。另外,它相对较新,属于 ES8 中的语法。但是转码器 Babel 已经支持,转码后就能使用。

async 相较于 Promise 的优势
1.相比于 Promise,它能更好地处理 then 链
  1. function takeLongTime(n) {
  2.     return new Promise(resolve => {
  3.         setTimeout(() => resolve(n + 200), n);
  4.     });
  5. }

  6. function step1(n) {
  7.     console.log(`step1 with ${n}`);
  8.     return takeLongTime(n);
  9. }

  10. function step2(n) {
  11.     console.log(`step2 with ${n}`);
  12.     return takeLongTime(n);
  13. }

  14. function step3(n) {
  15.     console.log(`step3 with ${n}`);
  16.     return takeLongTime(n);
  17. }
复制代码

现在用 Promise 方式来实现这三个步骤的处理。
  1. function doIt() {
  2.     console.time("doIt");
  3.     const time1 = 300;
  4.     step1(time1)
  5.         .then(time2 => step2(time2))
  6.         .then(time3 => step3(time3))
  7.         .then(result => {
  8.             console.log(`result is ${result}`);
  9.         });
  10. }
  11. doIt();
  12. // step1 with 300
  13. // step2 with 500
  14. // step3 with 700
  15. // result is 900
复制代码

如果用 async/await 来实现的话,会是这样:
  1. async function doIt() {
  2.     console.time("doIt");
  3.     const time1 = 300;
  4.     const time2 = await step1(time1);
  5.     const time3 = await step2(time2);
  6.     const result = await step3(time3);
  7.     console.log(`result is ${result}`);
  8. }
  9. doIt();
复制代码

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

2.中间值
现在把业务要求改一下,仍然是三个步骤,但每一个步骤都需要之前每个步骤的结果。Pomise的实现看着很晕,传递参数太过麻烦。
  1. function doIt() {
  2.     console.time("doIt");
  3.     const time1 = 300;
  4.     step1(time1)
  5.         .then(time2 => {
  6.             return step2(time1, time2)
  7.                 .then(time3 => [time1, time2, time3]);
  8.         })
  9.         .then(times => {
  10.             const [time1, time2, time3] = times;
  11.             return step3(time1, time2, time3);
  12.         })
  13.         .then(result => {
  14.             console.log(`result is ${result}`);
  15.         });
  16. }
  17. doIt();
复制代码

用 async/await 来写:
  1. async function doIt() {
  2.     console.time("doIt");
  3.     const time1 = 300;
  4.     const time2 = await step1(time1);
  5.     const time3 = await step2(time1, time2);
  6.     const result = await step3(time1, time2, time3);
  7.     console.log(`result is ${result}`);
  8. }
  9. doIt();
复制代码

没有多余的中间值,更加优雅地实现了。

3.调试
相比于 Promise 更易于调试。
因为没有代码块,所以不能在一个返回的箭头函数中设置断点。如果你在一个 .then 代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的 .then 代码块,因为调试器只能跟踪同步代码的每一步。


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



总结
每一个特性的出现,总有它的用途,而只有用了,才能更好地理解它。
JavaScript的异步编写方式,从 回调函数 到 Promise、Generator 再到 Async/Await。表面上只是写法的变化,但本质上则是语言层的一次次抽象。让我们可以用更简单的方式实现同样的功能,而不需要去考虑代码是如何执行的。

换句话说就是:异步编程的最高境界,就是根本不用关心它是不是异步




 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


1关注

13粉丝

75帖子

排行榜
关闭

站长推荐上一条 /1 下一条

官方微信

全国服务热线:

400-0708-360

公司地址:国家西部信息安全产业基地(成都市高新区云华路333号)

邮编:610000    Email:2908503813@qq.com

Copyright   ©2015-2016  EOIT论坛Powered by©Discuz!    ( 蜀ICP备11000634号-7 )