作者: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個方案及實現