作者:Valeri Karpov
翻译:疯狂的技术宅
原文:http://thecodebarbarian.com/a…
未经允许严禁转载
TC39 异步迭代器提案 将 for/await/of
引入了 JavaScript,还介绍了异步生成器函数的概念。现在 JavaScript 有 6 种不同的函数类型:
- 默认函数
function() {}
- 箭头函数
() => {}
-
异步函数
async function() {}
- 异步箭头函数
async () => {}
- 生成器函数
function*() {}
- 异步生成器函数
async function*() {}
异步生成器函数非常特殊,因为你可以在异步生成器函数中同时使用 await
和 yield
。异步生成器函数与异步函数和生成器函数的不同之处在于,它们不返回 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 个方案及实现
- 更多文章 …