乐趣区

JavaScript中的异步生成器函数

作者:Valeri Karpov

翻译:疯狂的技术宅

原文:http://thecodebarbarian.com/a…

未经允许严禁转载

TC39 异步迭代器提案 将 for/await/of 引入了 JavaScript,还介绍了异步生成器函数的概念。现在 JavaScript 有 6 种不同的函数类型:

  • 默认函数 function() {}
  • 箭头函数 () => {}
  • 异步函数 async function() {}
  • 异步箭头函数 async () => {}
  • 生成器函数 function*() {}
  • 异步生成器函数 async function*() {}

异步生成器函数非常特殊,因为你可以在异步生成器函数中同时使用 awaityield。异步生成器函数与异步函数和生成器函数的不同之处在于,它们不返回 promise 或迭代器,而是返回一个异步迭代器。你可以将异步迭代器视为 iterator,其 next() 函数始终会返回 promise。

你的第一个异步生成器函数

异步生成器函数的行为类似于生成器函数:生成器函数返回一个具有 next() 函数的对象,调用 next() 将执行生成器函数直到下一个 yield。不同之处在于异步迭代器的 next() 函数返回了一个 promise。

下面是带有异步生成器功能的“Hello, World”例子。请注意,以下脚本不适用于 Node.js 10.x 之前的版本。

'use strict';

async function* run() {await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

// `run()` returns an async iterator.
const asyncIterator = run();

// The function doesn't start running until you call `next()`
asyncIterator.next().
  then(obj => console.log(obj.value)). // Prints "Hello"
  then(() => asyncIterator.next());  // Prints "World"

遍历整个异步生成器函数的最干净方法是使用 for/await/of 循环。

'use strict';

async function* run() {await new Promise(resolve => setTimeout(resolve, 100));
  yield 'Hello';
  console.log('World');
}

const asyncIterator = run();

// Prints "Hello\nWorld"
(async () => {for await (const val of asyncIterator) {console.log(val); // Prints "Hello"
  }
})();

实际用例

你可能会想:“当 JavaScript 已经具有异步功能和生成器功能时,为什么还需要异步生成器功能?”一个用例是 Ryan Dahl 最初用 Node.js 来解决的经典进度条问题。

假设你要循环浏览 Mongoose cursor 中的所有文档,并通过 websocket 或命令行报告进度。

'use strict';

const mongoose = require('mongoose');

async function* run() {await mongoose.connect('mongodb://localhost:27017/test', { useNewUrlParser: true});
  await mongoose.connection.dropDatabase();

  const Model = mongoose.model('Test', mongoose.Schema({ name: String}));
  for (let i = 0; i < 5; ++i) {await Model.create({ name: `doc ${i}` });
  }

  // Suppose you have a lot of documents and you want to report when you process
  // each one. You can `yield` after processing each individual doc.
  const total = 5;
  const cursor = Model.find().cursor();

  let processed = 0;
  for await (const doc of cursor) {
    // You can think of `yield` as reporting "I'm done with one unit of work"
    yield {processed: ++processed, total};
  }
}

(async () => {for await (const val of run()) {
    // Prints "1 / 5", "2 / 5", "3 / 5", etc.
    console.log(`${val.processed} / ${val.total}`);
  }
})();

异步生成器函数使你的异步函数可以轻松地在 framework-free 中报告其进度。无需显式创建 websocket 或登录控制台 – 如果你的业务逻辑使用 yield 进行进度报告,则可以单独处理。

Observables

异步迭代器很棒,但是还有另一个并发原语:RxJS observables,异步生成器函数可以很好地与之配合。

'use strict';

const {Observable} = require('rxjs');
const mongoose = require('mongoose');

async function* run() {// Same as before}

// Create an observable that emits each value the async generator yields
// to subscribers.
const observable = Observable.create(async (observer) => {for await (const val of run()) {observer.next(val);
  }
});

// Prints "1 / 5", "2 / 5", "3 / 5", etc.
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));

使用可观察的 RxJS 与异步迭代器有两个主要区别。首先,在上面的示例中,在 subscribe() 中记录到控制台的代码是响应式的,而不是命令式的。换句话说,subscribe() handler 无法影响异步函数主体中的代码,它仅对事件做出反应。例如,使用 for/await/of 循环时,你可以在恢复异步生成器函数之前添加 1 秒的暂停时间。

(async () => {for await (const val of run()) {
    // Prints "1 / 5", "2 / 5", "3 / 5", etc.
    console.log(`${val.processed} / ${val.total}`);
    // This adds a 1 second delay to every `yield` statement.
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
})();

第二个是,由于 RxJS 可观察变量默认情况下是冷操作,新的 subscribe() 调用将重新执行该函数。

// Prints "1 / 5", "2 / 5", "3 / 5", etc.
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));
// Kicks off a separate instance of `run()`
observable.subscribe(val => console.log(`${val.processed} / ${val.total}`));

总结

异步生成器函数乍一看似乎有些小众并令人困惑,但是它们提供了为 JavaScript 解决进度条问题的本地解决方案。使用 yield 报告异步函数的进度是一个很诱人的想法,因为它使你可以将业务逻辑与进度报告框架分离。下次需要实现进度条时,请试试异步生成器。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:

  • 深入理解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你提高开发效率的现代 CSS 框架
  • 快速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你需要知道的一切
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 关于 Git 的 20 个面试题
  • 深入解析 Node.js 的 console.log
  • Node.js 究竟是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩展插件
  • Node.js 多线程完全指南
  • 把 HTML 转成 PDF 的 4 个方案及实现

  • 更多文章 …
退出移动版