JavaScript中的異步生成器函數

做者:Valeri Karpov

翻譯:瘋狂的技術宅javascript

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

未經容許嚴禁轉載前端

TC39異步迭代器提案for/await/of 引入了 JavaScript,還介紹了異步生成器函數的概念。如今 JavaScript 有 6 種不一樣的函數類型:vue

  • 默認函數 function() {}
  • 箭頭函數 () => {}
  • 異步函數 async function() {}
  • 異步箭頭函數 async () => {}
  • 生成器函數 function*() {}
  • 異步生成器函數 async function*() {}

異步生成器函數很是特殊,由於你能夠在異步生成器函數中同時使用 awaityield。異步生成器函數與異步函數和生成器函數的不一樣之處在於,它們不返回 promise 或迭代器,而是返回一個異步迭代器。你能夠將異步迭代器視爲 iterator,其 next() 函數始終會返回 promise。java

你的第一個異步生成器函數

異步生成器函數的行爲相似於生成器函數:生成器函數返回一個具備 next() 函數的對象,調用 next() 將執行生成器函數直到下一個 yield。不一樣之處在於異步迭代器的 next() 函數返回了一個 promisenode

下面是帶有異步生成器功能的 「Hello, World」 例子。請注意,如下腳本不適用於 Node.js 10.x 以前的版本。git

'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 來解決的經典進度條問題github

假設你要循環瀏覽 Mongoose cursor 中的全部文檔,並經過 websocket 或命令行報告進度。web

'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 報告異步函數的進度是一個很誘人的想法,由於它使你能夠將業務邏輯與進度報告框架分離。下次須要實現進度條時,請試試異步生成器。


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索