• 售前

  • 售后

热门帖子
入门百科

Node.js中的异步生成器与异步迭代详解

[复制链接]
大路84 显示全部楼层 发表于 2021-10-25 20:30:39 |阅读模式 打印 上一主题 下一主题
前言

生成器函数在 JavaScript 中的出现早于引入 async/await,这意味着在创建异步生成器(始终返回 Promise 且可以 await 的生成器)的同时,还引入了许多需要留意的事项。
本日,我们将研究异步生成器及其嫡亲——异步迭代。
留意:只管这些概念应该适用于所有依照当代规范的 javascript,但本文中的所有代码都是针对 Node.js 10、12 和 14 版开发和测试的。
异步生成器函数


看一下这个小步调:
  1. // File: main.js
  2. const createGenerator = function*(){
  3. yield 'a'
  4. yield 'b'
  5. yield 'c'
  6. }
  7. const main = () => {
  8. const generator = createGenerator()
  9. for (const item of generator) {
  10. console.log(item)
  11. }
  12. }
  13. main()
复制代码
这段代码界说了一个生成器函数,用该函数创建了一个生成器对象,然后用 for ... of 循环遍历该生成器对象。相当标准的东西——只管你绝不会在实际工作中用生成器来处理云云琐碎的事变。假如你不认识生成器和 for ... of 循环,请看《Javascript 生成器》 和 《ES6 的循环和可迭代对象的》 这两篇文章。在使用异步生成器之前,你需要对生成器和 for ... of 循环有扎实的相识。
假设我们要在生成器函数中使用 await,只要需要用 async 关键字声明函数,Node.js 就支持这个功能。假如你不认识异步函数,那么请看 《在当代 JavaScript 中编写异步任务》一文。
下面修改步调并在生成器中使用 await。
  1. // File: main.js
  2. const createGenerator = async function*(){
  3. yield await new Promise((r) => r('a'))
  4. yield 'b'
  5. yield 'c'
  6. }
  7. const main = () => {
  8. const generator = createGenerator()
  9. for (const item of generator) {
  10. console.log(item)
  11. }
  12. }
  13. main()
复制代码
同样在实际工作中,你也不会如许做——你可能会 await 来自第三方 API 或库的函数。为了能让各人轻松掌握,我们的例子只管保持简单。
假如实验运行上述步调,则会遇到标题:
  1. $ node main.js
  2. /Users/alanstorm/Desktop/main.js:9
  3. for (const item of generator) {
  4. ^
  5. TypeError: generator is not iterable
复制代码
JavaScript 告诉我们这个生成器是“不可迭代的”。乍一看,似乎使生成器函数异步也意味着它生成的生成器是不可迭代的。这有点令人狐疑,因为生成器的目的是生成“以编程方式”可迭代的对象。
接下来搞清楚到底发生了什么。
检查生成器


假如你看了 Javascript 生成器[1]的可迭代对象。当对象具有 next 方法时,该对象将实现迭代器协议,而且该 next 方法返回带有 value 属性,done 属性之一或同时带有 value 和 done 属性的对象。
假如用下面这段代码比力异步生成器函数与常规生成器函数返回的生成器对象:
  1. // File: test-program.js
  2. const createGenerator = function*(){
  3. yield 'a'
  4. yield 'b'
  5. yield 'c'
  6. }
  7. const createAsyncGenerator = async function*(){
  8. yield await new Promise((r) => r('a'))
  9. yield 'b'
  10. yield 'c'
  11. }
  12. const main = () => {
  13. const generator = createGenerator()
  14. const asyncGenerator = createAsyncGenerator()
  15. console.log('generator:',generator[Symbol.iterator])
  16. console.log('asyncGenerator',asyncGenerator[Symbol.iterator])
  17. }
  18. main()
复制代码
则会看到,前者没有 Symbol.iterator 方法,而后者有。
  1. $ node test-program.js
  2. generator: [Function: [Symbol.iterator]]
  3. asyncGenerator undefined
复制代码
这两个生成器对象都有一个 next 方法。假如修改测试代码来调用这个 next 方法:
  1. // File: test-program.js
  2. /* ... */
  3. const main = () => {
  4. const generator = createGenerator()
  5. const asyncGenerator = createAsyncGenerator()
  6. console.log('generator:',generator.next())
  7. console.log('asyncGenerator',asyncGenerator.next())
  8. }
  9. main()
复制代码
则会看到另一个标题:
  1. $ node test-program.js
  2. generator: { value: 'a', done: false }
  3. asyncGenerator Promise { <pending> }
复制代码
为了使对象可迭代,next 方法需要返回带有 value 和 done 属性的对象。一个 async 函数将总是返回一个 Promise 对象。这个特性会带到用异步函数创建的生成器上——这些异步生成器始终会 yield 一个 Promise 对象。
这种举动使得 async 函数的生成器无法实现 javascript 迭代协议。
异步迭代


荣幸的是有办法办理这个抵牾。假如看一看 async 生成器返回的构造函数或类
  1. // File: test-program.js
  2. /* ... */
  3. const main = () => {
  4. const generator = createGenerator()
  5. const asyncGenerator = createAsyncGenerator()
  6. console.log('asyncGenerator',asyncGenerator)
  7. }
复制代码
可以看到它是一个对象,其类型或类或构造函数是 AsyncGenerator 而不是 Generator:
  1. asyncGenerator Object [AsyncGenerator] {}
复制代码
只管该对象有可能不是可迭代的,但它是异步可迭代的。
要想使对象可以或许异步迭代,它必须实现一个 Symbol.asyncIterator 方法。这个方法必须返回一个对象,该对象实现了异步版本的迭代器协议。也就是说,对象必须具有返回 Promise 的 next 方法,而且这个 promise 必须最终剖析为带有 done 和 value 属性的对象。
一个 AsyncGenerator 对象满足所有这些条件。
这就留下了一个标题——我们怎样才华遍历一个不可迭代但可以异步迭代的对象?
for await … of 循环


只用生成器的 next 方法就可以手动迭代异步可迭代对象。(留意,这里的 main 函数现在是 async main ——如许可以或许使我们在函数内部使用 await)
  1. // File: main.js
  2. const createAsyncGenerator = async function*(){
  3. yield await new Promise((r) => r('a'))
  4. yield 'b'
  5. yield 'c'
  6. }
  7. const main = async () => {
  8. const asyncGenerator = createAsyncGenerator()
  9. let result = {done:false}
  10. while(!result.done) {
  11. result = await asyncGenerator.next()
  12. if(result.done) { continue; }
  13. console.log(result.value)
  14. }
  15. }
  16. main()
复制代码
但是,这不是最直接的循环机制。我既不喜欢 while 的循环条件,也不想手动检查 result.done。另外, result.done 变量必须同时存在于内部和外部块的作用域内。
荣幸的是大多数(大概是所有?)支持异步迭代器的 javascript 实现也都支持特别的 for await ... of 循环语法。比方:
  1. const createAsyncGenerator = async function*(){
  2. yield await new Promise((r) => r('a'))
  3. yield 'b'
  4. yield 'c'
  5. }
  6. const main = async () => {
  7. const asyncGenerator = createAsyncGenerator()
  8. for await(const item of asyncGenerator) {
  9. console.log(item)
  10. }
  11. }
  12. main()
复制代码
假如运行上述代码,则会看到异步生成器与可迭代对象已被乐成循环,而且在循环体中得到了 Promise 的完全剖析值。
  1. $ node main.js
  2. a
  3. b
  4. c
复制代码
这个 for await ... of 循环更喜欢实现了异步迭代器协议的对象。但是你可以用它遍历任何一种可迭代对象。
  1. for await(const item of [1,2,3]) {
  2. console.log(item)
  3. }
复制代码
当你使用 for await 时,Node.js 将会首先在对象上寻找 Symbol.asyncIterator 方法。假如找不到,它将回退到使用 Symbol.iterator 的方法。
非线性代码实行


与 await 一样,for await 循环会将非线性代码实行引入步调中。也就是说,你的代码将会以和编写的代码不同的顺序运行。
当你的步调第一次遇到 for await 循环时,它将在你的对象上调用 next。
该对象将 yield 一个 promise,然后代码的实行将会离开你的 async 函数,而且你的步调将继承在该函数之外实行。
一旦你的 promise 得到办理,代码实行将会使用这个值返回到循环体。
当循环结束并举行下一个行程时,Node.js 将在对象上调用 next。该调用会产生另一个 promise,代码实行将会再次离开你的函数。重复这种模式,直到 Promise 剖析为 done 为 true 的对象,然后在 for await 循环之后继承实行代码。
下面的例子可以分析一点:
  1. let count = 0
  2. const getCount = () => {
  3. count++
  4. return `${count}. `
  5. }
  6. const createAsyncGenerator = async function*() {
  7. console.log(getCount() + 'entering createAsyncGenerator')
  8. console.log(getCount() + 'about to yield a')
  9. yield await new Promise((r)=>r('a'))
  10. console.log(getCount() + 're-entering createAsyncGenerator')
  11. console.log(getCount() + 'about to yield b')
  12. yield 'b'
  13. console.log(getCount() + 're-entering createAsyncGenerator')
  14. console.log(getCount() + 'about to yield c')
  15. yield 'c'
  16. console.log(getCount() + 're-entering createAsyncGenerator')
  17. console.log(getCount() + 'exiting createAsyncGenerator')
  18. }
  19. const main = async () => {
  20. console.log(getCount() + 'entering main')
  21. const asyncGenerator = createAsyncGenerator()
  22. console.log(getCount() + 'starting for await loop')
  23. for await(const item of asyncGenerator) {
  24. console.log(getCount() + 'entering for await loop')
  25. console.log(getCount() + item)
  26. console.log(getCount() + 'exiting for await loop')
  27. }
  28. console.log(getCount() + 'done with for await loop')
  29. console.log(getCount() + 'leaving main')
  30. }
  31. console.log(getCount() + 'before calling main')
  32. main()
  33. console.log(getCount() + 'after calling main')
复制代码
这段代码你用了编号的日志记录语句,可让你跟踪着实行情况。作为练习,你需要本身运行步调然后查看实行结果是怎样的。
假如你不知道它的工作方式,就会使步调的实行产生紊乱,但异步迭代的确是一项强大的技能。
总结

到此这篇关于Node.js中异步生成器与异步迭代的文章就先容到这了,更多相关Node.js异步生成器与异步迭代内容请搜索脚本之家从前的文章或继承浏览下面的相关文章希望各人以后多多支持脚本之家!

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作