Async Generator Functions in JavaScript

原文《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ì)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容