ES2018 新特性
- 非同步迭代器(本文)
- 正規表示式反向(lookbehind)斷言
- 正規表示式 Unicode 轉義
- 非轉義序列的模板字串
- 正規表示式 s/dotAll 模式
- 正規表示式命名捕獲組
- 物件展開運算子
- Promise.prototype.finally
1. 概述
在 ECMAScript 2015(ES6) 中 JavaScript 引入了迭代器介面(iterator)用來遍歷資料。迭代器物件知道如何每次訪問集合中的一項, 並跟蹤該序列中的當前位置。在 JavaScript 中迭代器是一個物件,它提供了一個 next()
方法,用來返回序列中的下一項。這個方法返回包含兩個屬性:done
和 value
。
迭代器物件一旦被建立,就可以反覆呼叫 next()
。
function makeIterator(array) {
let nextIndex = 0; // 初始索引
// 返回一個迭代器物件,物件的屬性是一個 next 方法
return {
next: function() {
if (nextIndex < array.length) {
// 當沒有到達末尾時,返回當前值,並把索引加1
return { value: array[nextIndex++], done: false };
}
// 到達末尾,done 屬性為 true
return {done: true};
}
};
}
複製程式碼
一旦初始化,next()
方法可以用來依次訪問物件中的鍵值:
const it = makeIterator(['j', 'u', 's', 't']);
it.next().value; // j
it.next().value; // u
it.next().value; // s
it.next().value; // t
it.next().value; // undefined
it.next().done; // true
it.next().value; // undefined
複製程式碼
2. 可迭代物件
一個定義了迭代行為的物件,比如在 for...of
中迴圈了哪些值。為了實現可迭代,一個物件必須實現 @@iterator
方法,這意味著這個物件(或其原型鏈中的一個物件)必須具有帶 Symbol.iterator
鍵的屬性:
String
,Array
,TypedArray
,Map
和 Set
都內建可迭代物件,因為它們的原型物件都有一個 Symbol.iterator
方法。
const justjavac = {
[Symbol.iterator]: () => {
const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];
return {
next: () => ({
done: items.length === 0,
value: items.shift()
})
}
}
}
複製程式碼
當我們定義了可迭代物件後,就可以在 Array.from
、for...of
中使用這個物件:
[...justjavac];
// ["j", "u", "s", "t", "j", "a", "v", "a", "c"]
Array.from(justjavac)
// ["j", "u", "s", "t", "j", "a", "v", "a", "c"]
new Set(justjavac);
// {"j", "u", "s", "t", "a", "v", "c"}
for (const item of justjavac) {
console.log(item)
}
// j
// u
// s
// t
// j
// a
// v
// a
// c
複製程式碼
3. 同步迭代
由於在迭代器方法返回時,序列中的下一個值和資料來源的 "done" 狀態必須已知,所以迭代器只適合於表示同步資料來源。
雖然 JavaScript 程式設計師遇到的許多資料來源是同步的(比如記憶體中的列表和其他資料結構),但是其他許多資料來源卻不是。例如,任何需要 I/O 訪問的資料來源通常都會使用基於事件的或流式非同步 API 來表示。不幸的是,迭代器不能用來表示這樣的資料來源。
(即使是 promise 的迭代器也是不夠的,因為它的 value 是非同步的,但是迭代器需要同步確定 "done" 狀態。)
為了給非同步資料來源提供通用的資料訪問協議,我們引入了 AsyncIterator
介面,非同步迭代語句(for-await-of
)和非同步生成器函式。
4. 非同步迭代器
一個非同步迭代器就像一個迭代器,除了它的 next()
方法返回一個 { value, done }
的 promise。如上所述,我們必須返回迭代器結果的 promise,因為在迭代器方法返回時,迭代器的下一個值和“完成”狀態可能未知。
我們修改一下之前的程式碼:
const justjavac = {
- [Symbol.iterator]: () => {
+ [Symbol.asyncIterator]: () => {
const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];
return {
- next: () => ({
+ next: () => Promise.resolve({
done: items.length === 0,
value: items.shift()
})
}
}
}
複製程式碼
好的,我們現在有了一個非同步迭代器,程式碼如下:
const justjavac = {
[Symbol.asyncIterator]: () => {
const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];
return {
next: () => Promise.resolve({
done: items.length === 0,
value: items.shift()
})
}
}
}
複製程式碼
我們可以使用如下程式碼進行遍歷:
for await (const item of justjavac) {
console.log(item)
}
複製程式碼
如果你遇到了 SyntaxError: for await (... of ...) is only valid in async functions and async generators
錯誤,那是因為 for-await-of
只能在 async 函式或者 async 生成器裡面使用。
修改一下:
(async function(){
for await (const item of justjavac) {
console.log(item)
}
})();
複製程式碼
5. 同步迭代器 vs 非同步迭代器
5.1 Iterators
// 迭代器
interface Iterator {
next(value) : IteratorResult;
[optional] throw(value) : IteratorResult;
[optional] return(value) : IteratorResult;
}
// 迭代結果
interface IteratorResult {
value : any;
done : bool;
}
複製程式碼
5.2 Async Iterators
// 非同步迭代器
interface AsyncIterator {
next(value) : Promise<IteratorResult>;
[optional] throw(value) : Promise<IteratorResult>;
[optional] return(value) : Promise<IteratorResult>;
}
// 迭代結果
interface IteratorResult {
value : any;
done : bool;
}
複製程式碼
6. 非同步生成器函式
非同步生成器函式與生成器函式類似,但有以下區別:
- 當被呼叫時,非同步生成器函式返回一個物件,"async generator",含有 3 個方法(
next
,throw
,和return
),每個方法都返回一個 Promise,Promise 返回{ value, done }
。而普通生成器函式並不返回 Promise,而是直接返回{ value, done }
。這會自動使返回的非同步生成器物件具有非同步迭代的功能。 - 允許使用
await
表示式和for-await-of
語句。 - 修改了
yield*
的行為以支援非同步迭代。
示例:
async function* readLines(path) {
let file = await fileOpen(path);
try {
while (!file.EOF) {
yield await file.readLine();
}
} finally {
await file.close();
}
}
複製程式碼
函式返回一個非同步生成器(async generator)物件,可以用在 for-await-of
語句中。
7. 實現
- Chakra - 暫未支援
- JavaScriptCore - Safari Tech Preview 40
- SpiderMonkey - Firefox 57
- V8 - Chrome 63
Polyfills
Facebook 的 Regenerator 專案為 AsyncIterator
介面提供了一個 polyfill,將非同步生成器函式變成返回 AsyncIterator
的物件 ECMAScript 5 函式。Regenerator 還不支援 for-await-of
非同步迭代語法。
Babylon parser 專案支援非同步生成器函式和 for- await-of
語句(v6.8.0+)。你可以使用它的 asyncGenerators 外掛。
require("babylon").parse("code", {
sourceType: "module",
plugins: [
"asyncGenerators"
]
});
複製程式碼
Additionally, as of 6.16.0, async iteration is included in Babel under the the name "babel-plugin-transform-async-generator-functions" as well as with babel-preset-stage-3. Note that the semantics implemented there are slightly out of date compared to the current spec text in various edge cases.
另外,從 6.16.0 開始,非同步迭代被包含在 Babel 的 "babel-plugin-transform-async-generator-functions"
下以及 babel-preset-stage-3
。
require("babel-core").transform("code", {
plugins: [
"transform-async-generator-functions"
]
});
複製程式碼