JavaScript 异步编程指南 — 解决方案 Async/Await

开发 前端
ES7 之后引入了 Async/Await 解决异步编程,这种方式在 JavaScript 异步编程中目前也被称为 “终极解决方案”。

[[407624]]

本文转载自微信公众号「五月君」,作者五月君。转载本文请联系五月君公众号。

ES7 之后引入了 Async/Await 解决异步编程,这种方式在 JavaScript 异步编程中目前也被称为 “终极解决方案”。

基本使用

函数声明时在 function 关键词之前使用 async 关键字,内部使用 await 替换了 Generator 中的 yield,语义上比起 Generator 中的 * 号也更明确。

在执行时相比 Generator 而言,Async/Await 内置执行器,不需要 co 这样的外部模块,程序语言本身实现是最好的,使用上也更简单。

声明 async 函数

以下是基于 Generator 一讲中的一个例子做了改造,在第二个 await 后面,使用 Promise 封装了下,它本身是支持跟一个 Promise 对象的,这个时候它会等待当 Promise 状态变为 Fulfilled 才会执行下一步,当 Promise 未能正常执行 resolve/reject 时那就意味着,下面的也将得不到执行。

await 后面还可跟上基本类型:数值、字符串、布尔值,但这时也会立即转成 Fulfilled 状态的 Promise。

  1. async function test() { 
  2.   const res1 = await 'A'
  3.   const res2 = await Promise.resolve(res1 + 'B'); 
  4.   const res3 = await res2 + 'C'
  5.   return res3; 
  6.  
  7. (async () => { 
  8.   const res = await test(); // ABC 
  9. })(); 

错误管理

如果 await 后面的 Promise 返回一个错误,需要 try/catch 做错误捕获,若有多个 await 操作也可都放在一起。这种情况,假如第一个 await 后面的 Promise 报错,第二个 await 是不会执行的。

这和普通函数操作基本上是一样的,不同的是对于异步函数我们需要加上 await 关键字。

  1. (async () => { 
  2.   try { 
  3.    await fetch1(url); 
  4.    await fetch2(url); 
  5.   } catch (err) { 
  6.    // TODO 
  7.   } 
  8. })(); 

也要注意 await 必须写在 async 函数里,否则会报错 SyntaxError: await is only valid in async functions and the top level bodies of modules。

  1. // 错误的操作 
  2. (() => { 
  3.   await 'A'
  4. })(); 

这样写也是不行的,在 “协程” 一讲中也提过类似的示例,只不过当时是基于 yield 表达式,async/await 实际上是 Generator 函数的一种语法糖,内部机制是一样的,forEach 里面的匿名函数是一个普通的函数,运行时会被看作是一个子函数,栈式协程是从子函数产生的,而 ES6 中实现的协程属于无堆栈式协程,只能从生成器内部生成。以下代码在运行时会直接失败。

  1. (async () => { 
  2.   ['B''C'].forEach(item => { 
  3.     const res = await item; 
  4.     console.log(res); 
  5.   }) 
  6. })(); 

想通过 await 表达式正常运行,就要避免使用回调函数,可以使用遍历器 for...of。

  1. (async () => { 
  2.   for (const item of ['B''C']) { 
  3.     const res = await item; // B C 
  4.   } 
  5. })(); 

并发执行

当我们拥有多个异步请求,且不必顺序执行时,可以在 await 表达式后使用 Promise.all(),这是一个很好的实践。

  1. (async () => { 
  2.   await Promise.all([ 
  3.    fetch(url1), 
  4.     fetch(ur2) 
  5.   ]) 
  6. })(); 

通过这个示例可以看出,async/await 也还是基于 Promise 的。

异步迭代

上面讲解的使用 Async/Await 都是基于单次运行的异步函数,在 Node.js 中我们还有一类需求它来自于连续的事件触发,例如,基于流式 API 读取数据,常见的是注册 on('data', callback) 事件和回调函数,但是这样我们不能利用常规的 Async/Await 表达式来处理这类场景。

异步迭代器

异步迭代器与同步迭代器不同的是,一个可迭代的异步迭代器对象具有 [Symbol.asyncIterator] 属性,并且返回的是一个 Promise.resolve({ value, done }) 结果。

实现异步迭代器比较方便的方式是使用声明为 async 的生成器函数,可以使我们像常规函数中一样去使用 await,以下展示了 Node.js 可读流对象是如何实现的异步可迭代,只列出了核心代码,异步迭代器笔者也有一篇详细的文章介绍,很精彩,感兴趣的可以看看 探索异步迭代器在 Node.js 中的使用。

  1. // for await...of 循环会调用 
  2. Readable.prototype[SymbolAsyncIterator] = function() { 
  3.   ... 
  4.   const iter = createAsyncIterator(stream); 
  5.   return iter; 
  6. }; 
  7.  
  8. // 声明一个创建异步迭代器对象的生成器函数 
  9. async function* createAsyncIterator(stream) { 
  10.   ... 
  11.   try { 
  12.     while (true) { 
  13.       // stream.read() 从内部缓冲拉取并返回数据。如果没有可读的数据,则返回 null 
  14.       // readable 的 destroy() 方法被调用后 readable.destroyed 为 true,readable 即为下面的 stream 对象 
  15.       const chunk = stream.destroyed ? null : stream.read(); 
  16.       if (chunk !== null) { 
  17.         yield chunk; // 这里是关键,根据迭代器协议定义,迭代器对象要返回一个 next() 方法,使用 yield 返回了每一次的值 
  18.       } 
  19.       ... 
  20.     } 
  21.   } catch (err) { 
  22.   } 

for...await...of 遍历器

Node.js Stream 模块的可读流对象在 v10.0.0 版本试验性的支持了 [Symbol.asyncIterator] 属性,可以使用 for await...of 语句遍历可读流对象,在 v11.14.0 版本以上已 LTS 支持,这使得我们从流中读取连续的数据块变的很方便。

  1. const fs = require('fs'); 
  2. const readable = fs.createReadStream('./hello.txt', { encoding: 'utf-8' }); 
  3.  
  4. async function readText(readable) { 
  5.   let data = ''
  6.   for await (const chunk of readable) { 
  7.     data += chunk; 
  8.   } 
  9.   return data; 
  10.  
  11. (async () => { 
  12.   try { 
  13.     const res = await readText(readable); 
  14.     console.log(res); // Hello Node.js 
  15.   } catch (err) { 
  16.     console.log(err.message); 
  17.   } 
  18. })(); 

使用 **for await...of** 语句遍历 readable,如果循环中因为 break 或 throw 一个错误而终止,则这个 Stream 也将被销毁。

顶级 Await

根据 async 函数语法规则,await 只能出现在 async 异步函数内。对于异步资源,之前我们必须在 async 函数内才可使用 await,这对一些在文件顶部需要实例化的资源可能会不好操作。

在 Node.js v14.x LTS 发布后,已支持顶级 Await 我们可以方便的在文件顶部对这些异步资源做一些初始化操作。

我们可以像下面这样来写,但这种模式也只有在 ES Modules 中才可用。

  1. import fetch from 'node-fetch'
  2. const res = await fetch(url) 

总结

JavaScript 编程中大部分操作都是异步编程,Async/Await 可以已同步的方式来书写我们的代码,但是实际执行其还是异步的,这种被方式目前也称为异步编程的终极解决方案。

 

责任编辑:武晓燕 来源: 五月君
相关推荐

2017-08-02 14:17:08

前端asyncawait

2014-07-15 10:08:42

异步编程In .NET

2016-11-22 11:08:34

asyncjavascript

2023-07-28 07:31:52

JavaScriptasyncawait

2021-07-20 10:26:12

JavaScriptasyncawait

2021-11-01 22:36:04

JavaScript

2018-05-13 21:57:04

JavaScript异步编程方案

2013-05-16 10:33:11

C#C# 5.0Async

2021-06-06 19:51:07

JavaScript异步编程

2023-11-03 14:32:38

2020-10-15 13:29:57

javascript

2018-12-19 18:40:28

JavaScriptes6 前端

2023-04-14 08:10:59

asyncawait

2021-02-09 09:53:11

C#多线程异步

2014-07-15 10:31:07

asyncawait

2013-03-14 11:18:30

Microsoft A解决方案

2014-05-23 10:12:20

Javascript异步编程

2015-04-22 10:50:18

JavascriptJavascript异

2017-07-13 12:12:19

前端JavaScript异步编程

2016-09-07 20:43:36

Javascript异步编程
点赞
收藏

51CTO技术栈公众号