原文《Async Generator Functions in JavaScript》
翻譯:范小飯
將for/await/of引入javascript的TC39異步迭代器提議還引入了異步生成器函數(shù)的概念?,F(xiàn)在,javascript有6種不同的函數(shù)類(lèi)型。
- 普通函數(shù)
function() {} - 箭頭函數(shù)
() => {} -
異步函數(shù)
async function() {} - 異步箭頭函數(shù)
async () => {} - 生成器函數(shù)
function*() {} - 異步生成器函數(shù)
async function*() {}
異步生成器函數(shù)是特別的,因?yàn)槟憧梢栽诤瘮?shù)中同時(shí)使用await 和 yield, 異步生成器函數(shù)不同于異步函數(shù)和生成器函數(shù),因?yàn)樗粫?huì)返回promise或者iterator,而是返回異步迭代器,你可以把異步迭代器作為一個(gè)next()函數(shù)總是返回一個(gè)promise的迭代器。
你的第一個(gè)異步迭代器
異步迭代器函數(shù)行為與迭代器函數(shù)相似,迭代器函數(shù)返回一個(gè)有next函數(shù)的對(duì)象,調(diào)用next()將執(zhí)行g(shù)enerator函數(shù),直到下一個(gè)yield。不同的是,異步迭代器的next() 返回一個(gè)promise
下面是一個(gè)異步生成器函數(shù)的"Hello, World"的例子。請(qǐng)注意,下面的腳本在10.x之前的node.js版本上不起作用。
'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循環(huán)整個(gè)異步生成器函數(shù)。
'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"
}
})();
一個(gè)實(shí)際的用例
你可能在想,為什么有了異步函數(shù)和生成器函數(shù),JavaScript還需要異步生成器函數(shù)?一個(gè)用例是RyanDahl最初編寫(xiě)node.js來(lái)解決的經(jīng)典進(jìn)度條問(wèn)題。
假設(shè)您希望遍歷Mongoose光標(biāo)中的所有文檔,并通過(guò)WebSocket或命令行報(bào)告進(jìn)度。
'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}`);
}
})();
異步生成器函數(shù)使您的異步函數(shù)能夠以無(wú)框架的方式報(bào)告其進(jìn)度,不需要明確的創(chuàng)建websocket或者console.log,假設(shè)業(yè)務(wù)邏輯使用yield進(jìn)行進(jìn)度報(bào)告,你可以使用yield單獨(dú)處理。
可以觀測(cè)的
異步迭代器很棒,但還有另一個(gè)并發(fā)原語(yǔ),即異步生成器函數(shù)與RxJS observables也能很好的對(duì)應(yīng)上。
'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 observable和異步迭代器有2個(gè)關(guān)鍵區(qū)別,
1、在上面的示例中,在subscribe()中記錄到控制臺(tái)的代碼是反應(yīng)式的,而不是命令式的。換句話說(shuō),subscribe()處理程序無(wú)法影響異步函數(shù)體中的代碼,它只對(duì)事件作出反應(yīng),當(dāng)用一個(gè)for/await/of 循環(huán),例如,當(dāng)使用for/await/of循環(huán)時(shí),可以在恢復(fù)異步生成器函數(shù)之前添加一秒鐘的暫停
(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));
}
})();
第二個(gè)就是,由于RXJS observable在默認(rèn)情況下是冷的,所以一個(gè)新的subscribe()調(diào)用會(huì)重新執(zhí)行該函數(shù)。
// 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}`));
Moving On
異步生成器函數(shù)一開(kāi)始可能看起來(lái)令人困惑,但它們提供了可能成為javascript的進(jìn)度條問(wèn)題的本機(jī)解決方案,使用yield報(bào)告異步函數(shù)的進(jìn)度是一個(gè)誘人的想法,因?yàn)樗试S您將業(yè)務(wù)邏輯與進(jìn)度報(bào)告框架分離。下次需要實(shí)現(xiàn)進(jìn)度條時(shí),給異步生成器一個(gè)機(jī)會(huì)。