提示
: 本文是 github 上《Understanding ECMAScript 6》 的筆記整理,代碼示例也來源於此。你們有時間能夠直接讀這本書。雖是英文,但通俗易懂,很是推薦。git
前情:
在上一篇文章 你知道爲何會有 Generator 嗎 裏,我拋磚引玉,介紹了 generator
產生的緣由。當時就有夥伴指出 「Generator是用來模擬多線程的休眠機制的」、 「Generator運行是惰性的」。那時我就說高級篇裏會有介紹,這裏就好好說一下。es6
摘要:
這裏的重點,首先是如何與generator
裏通訊,一是用 next()
傳參,二是還能夠用 throw()
,不一樣的是它是往裏拋錯; 其次是有 yield
賦值語句時, generator
內部的執行順序; 最後會是怎麼用同步的方式寫異步(有可能像 co
哦)。github
原文地址redux
若是對 generator
不太熟的,能夠先看看 這裏promise
簡單說就是能夠往next
傳參數,而generator
裏 yield
處能夠接收到這個參數, 以下例子:bash
function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.next(5)); // "{ value: 8, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
要很明白地解釋上面的執行過程,可藉助這張圖:多線程
顏色相同的是同一次迭代裏執行的,由淺到深,表示迭代的前後順序。如:異步
next()
, 執行 yield 1
到中止,返回 { value: 1, done: false }
。注意,這時賦值語句 let fisrt = ...
沒有執行;next(4)
, 先將參數 4
傳入上一次 yield
處,可理解爲:let first = yield 1;
=>
let first = 4;
複製代碼
再從上次停頓的地方開始執行,就是說先執行賦值語句async
let first = 4
複製代碼
而後執行到下個yield
爲止,即函數
yield first + 2 // 4 + 2
複製代碼
最後返回 { value: 6, done: false }
以後的 next
依上面的原理而執行,直到迭代完畢。
也就是說,經過next
的參數,generator
產生的 iterator
,與外部環境搭建起了溝通的橋樑,結合 iterator
能夠停頓的特色,能夠作一些有意思的事,如用同步方式寫回調等,詳見下文。
iterator
裏拋錯function *createIterator() {
let first = yield 1;
let second = yield first + 2; // yield 4 + 2, 而後拋出錯誤
yield second + 3; // 不會被執行
}
let iterator = createIterator();
console.log(iterator.next()); // {value: 1, done: false}
console.log(iterator.next(4)); // {value: 6, done: false}
console.log(iterator.throw(new Error("Boom"))); // generator 裏拋出的錯誤
複製代碼
根據上面說的執行機制,這裏例子的執行流程能夠用這張圖表示:
第三次執行迭代時,咱們調用 iterator.throw(new Error("Boom"))
, 向 iterator
裏拋出錯誤,傳入的參數爲錯誤信息。
咱們能夠改造 createIterator
以下:
function* createIterator() {
let first = yield 1;
let second;
try {
second = yield first + 2;
} catch (ex) {
second = 6;
}
yield second + 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
其執行流程解釋以下:
前兩次調用 next
狀況和上面執行機制裏的分析是同樣的,就不贅述了。
第三次調用 iterator.throw(new Error("Boom")
往generator
往拋入錯誤,函數內部在上次中止處即 yield first + 2
接收信息,拋出錯誤。可是被catch
了,因此繼續執行到下一個停頓點:
yield second + 3; // 6 + 3
複製代碼
最後返回本次迭代結果 { value: 9, done: false }
繼續執行其餘迭代,和上沒無甚不一樣,不贅述。
小結: 這裏有能夠看到,
next()
和throw()
均可以讓iterator
繼續執行下去,不一樣的是後者會是以拋出錯誤的方式讓iterator
繼續執行的。但在這以後,generator
裏會發生什麼,取決於代碼怎麼寫的了。
Generator
裏的 return
語句這裏的 return
語句, 功能上與通常函數的 return
沒太大區別,都會阻止 return
以後的語句執行。
function* createIterator() {
yield 1;
return;
yield 2;
yield 3;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
上面的 return
, 使得以後的 yield
都被忽略了,因此,迭代二次而卒。
可是,若是 return
後有值,會被計入本次迭代的結果中:
function* createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 42, done: true }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
這個iterator
執行兩次就可收攤了,和上一個例子不一樣的是,最後一次返回結果裏有 return
後的值 { value: 42, done: true }
。
又可是,這個返回值只能用一次,因此第三次執行next
, 返回結果變成了 { value: undefined, done: true }
。
特別注意: 展開操做符...
和 for-of
看到迭代結果裏 done
是 true
就立刻中止執行,連 return
後面的值也無論了,中止得很決絕。如上面的例子,用for-of
和 ...
執行:
function* createIterator() {
yield 1;
return 42;
}
let iterator = createIterator();
for(let item of iterator) {
console.log(item);
}
// 1
let anotherIterator = createIterator();
console.log([...anotherIterator])
// [1]
// 猜猜 [...iterator] 的結果是什麼
複製代碼
generator
委託是什麼,簡單說就是把 generator
A 委託給 generator
B, 讓 B 代爲執行:
function* createNumberIterator() {
yield 1;
yield 2;
}
function* createColorIterator() {
yield "red";
yield "green";
}
function* createCombinedIterator() {
yield* createNumberIterator();
yield* createColorIterator();
yield true;
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "red", done: false }"
console.log(iterator.next()); // "{ value: "green", done: false }"
console.log(iterator.next()); // "{ value: true, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
以上可見,委託的語法,就是在一個 generator
裏, 用 yield*
操做另外一個 generator
的執行結果。
經過委託把不一樣的 generator
放一塊兒,再利用return
的返回值,能夠在 generator
裏通訊,給出了更多的想象空間:
function* createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function* createRepeatingIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
function* createCombinedIterator() {
let result = yield* createNumberIterator();
yield* createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
如上, createNumberIterator
的返回值 3
傳入了createRepeatingIterator
裏, 若是拆開寫,是這樣:
function* createNumberIterator() {
yield 1;
yield 2;
return 3;
}
function* createRepeatingIterator(count) {
for (let i = 0; i < count; i++) {
yield "repeat";
}
}
function* createCombinedIterator() {
let result = yield* createNumberIterator();
yield result;
yield* createRepeatingIterator(result);
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
複製代碼
注意:既然
yield *
後面接的是generator
的執行結果,而generator
是iterable
。就是說,yield *
後能夠直接跟iterable
, 如字符串。如:
let g = function *() {
yield *['a', 'b', 'c']
}
for(let item of g()) {
console.log(item);
}
// a
// b
// c
複製代碼
Genarator
與異步關於 js
裏異步的特色,這裏展開說了。簡單來說,它讓 js
這們單線程語言更強大; 可是,異步狀況一複雜好比有異步之間有依賴,那就很容易寫出以下的callback hell
, 極難維護:
合理利用 genarator
就能夠用同步的寫法,寫異步。
從以前的介紹裏已經知道,genarator
返回 iterator
, 須要手動調用 next
, 很麻煩。那若是封裝一些,可讓 iterator
本身執行完畢,不就很好了:
前期準備,實現自動執行 generator
的函數
run(function* () {
let value = yield 1;
console.log(value);
value = yield value + 3;
console.log(value);
});
複製代碼
要讓它本身執行,那麼 run
須要:
generator
, 拿到 iterator
;iterator.next()
;iterator.next(lastResult)
參數,繼續迭代;實現以下:
function run(taskDef) {
// 建立並保存 iterator,留到後面使用
let task = taskDef();
let result = task.next();
// 遞歸地執行 `next`
function step() {
// 若是沒完的話
if (!result.done) {
result = task.next(result.value);
step();
}
}
// 開始處理
step();
}
複製代碼
實現目標,用同步方式寫異步
加入咱們要讓下面這段代碼可行:
const asyncWork = new Promise((resolve, reject) => {
setTimeout(() => resolve(5), 500)
})
run(function* () {
let value = yield asyncWork;
console.log(value)
value = yield value + 3;
console.log(value)
});
複製代碼
這裏和上一個例子不一樣的地方在於,yield
返回結果多是個promise
, 那咱們加個判斷就能夠了:
if (result.value && typeof result.value.then === 'function') {
result.value.then(d => {
result = task.next(d)
...
})
}
複製代碼
就是判斷若是是 promise
, 執行 then
函數,把返回結果傳入下一次迭代 next(d)
便可。完整示例代碼以下:
function run(taskDef) {
// 建立並保存 iterator,留到後面使用
let task = taskDef();
let result = task.next();
// 遞歸地執行 `next`
function step() {
// 若是沒完的話
if (!result.done) {
if (result.value && typeof result.value.then === 'function') {
result.value.then(d => {
result = task.next(d)
step();
})
} else {
result = task.next(result.value);
step();
}
}
}
// 開始處理
step();
}
複製代碼
回頭看看這個寫法:
run(function* () {
let value = yield asyncWork;
console.log(value)
value = yield value + 3;
console.log(value)
});
複製代碼
雖然第二個 yield
對上一個 yield
結果有依賴,但不用寫成回調,看着跟同步同樣,很直白!
generator
產生的 iterator
, 能夠用next
,在函數外部往 generator
裏傳數據, 又能夠經過 throw
往裏拋錯。它們至關於在 generator
裏對外打開了多個通訊窗口,這讓清晰的異步成爲可能。強大的 redux-saga
也是基於 generator
實現的。是否是有更多的玩法?一切都是拋磚引玉,不知道你們還有其餘玩法沒?
若是對 generator
由來不太清楚的,也能夠先看看 這裏
另外,這篇文章最早發佈在 github,是個關於 ES6
的系列文章。若是以爲能夠,幫忙 star
下唄,方便找工做啊。哎,找工做,真-是-累-啊!!!