Javascript 中的異步生成器函數

先來複習幾個語法:javascript

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

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

寫一個異步生成器函數

異步生成器函數的行爲與生成器函數相似:生成器函數返回一個具備next()函數的對象,而且調用next()執行生成器函數直到下一個yield。 區別在於異步迭代器的next()函數返回一個promise。java

下面是一個帶有異步生成器函數的「Hello,World」示例。 請注意,如下腳本不適用於10.x以前的Node.js版本。es6

'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循環。web

pic

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

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

'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}`);
  }
})();
複製代碼

異步生成器函數使異步函數能夠輕鬆地以無框架方式報告其進度。 無需顯式建立websocket或日誌到控制檯 - 若是假設業務邏輯使用yield來進行進度報告,則能夠單獨處理。websocket

生成器函數

通常狀況下,咱們直接稱呼: generator 函數,而不是把它翻譯過來。generator 本質上也是一個函數,只是它能夠像迭代器同樣在中間某一步暫停,而後再從暫停處從新開始。簡單來講,generator 的表現像是一個具備迭代器行爲的函數。框架

另一個事實是: async/await 能夠基於 generator 實現。異步

能夠看看下面的代碼來理解 generator 是什麼:

function normalFunc() {
  console.log('I')
  console.log('cannot')
  console.log('be')
  console.log('stopped.')
}
複製代碼

上面的代碼若是想在運行時退出,惟一的方法使用 return 或者 throw 拋出一個錯誤。可是若是你再次調用,它會從頂部從新開始執行。generator 是一種特殊的函數,它簡化了迭代任務。generator 會產生一系列返回值而不是一個值。在 Javascript 中,generator 在你調用 next() 的時候返回一個對象。

{
  value: Any,
  done: true|false
}
複製代碼

這裏有一張形象的圖片:

建立一個 generator

function * generatorFunction() { // Line 1
  console.log('This will be executed first.');
  yield 'Hello, ';   // Line 2
  console.log('I will be printed after the pause');
  yield 'World!';
}
const generatorObject = generatorFunction(); // Line 3
console.log(generatorObject.next().value); // Line 4
console.log(generatorObject.next().value); // Line 5
console.log(generatorObject.next().value); // Line 6
// This will be executed first.
// Hello,
// I will be printed after the pause
// World!
// undefined
複製代碼

咱們來逐句解讀一下:首先是 function * 。這是一個 generator 的標誌。函數體內部沒有 return 咱們用了 yeild 代替。這個操做符就起到了暫停而且返回的做用。直到下一次調用的時候,從上次 yeild 處重新開始。固然也能夠 return ,不過這時候會設置 done 屬性爲 true。也就是說,當你再次 next 的時候,會獲得 done: true 的鍵值對。

在第三行咱們調用了 generator 去生成一個 generator 對象。由於generator 函數總會返回一個 generator 對象,這個對象是能夠被迭代的,也就是說它能夠放在循環之中。第四行咱們調用了 generator 對象上的 next 方法。這個時候 第二行的的 yield 會執行,而後返回一個 { value: 'Hello, ', done: false } 格式接着掛起。當第五行 next 被調用時,首先執行了console.log 接着,返回了 Wolrd。而後generator 繼續休眠。第六行咱們繼續調用。記着若是沒有函數顯式返回值的話。那麼他們會默認返回 undefined。因此,在最後咱們獲得了 undefined。

最後咱們補充一點:async/await 其實就是 generator 的語法糖

const gen = function* () {
  yield "hello";
};


const genAntoerh = async function() {
  await "hello";
}
複製代碼

以上兩個函數是等價的。

參考資料

相關文章
相關標籤/搜索