[JS] generators

接續上一篇的 iterator,來看看 generators 到底是什麼?

Generator function (function*)

1
2
3
function* name([param[, param[, ... param]]]) {
statements
}

一個 generator 函示,使用 function* 作為宣告,會回傳一個 Generator 物件。

generator 可以被中斷跳出,等到再次呼叫的時候再繼續執行。

呼叫 function* 並不會立刻執行函式內容,而是會回傳一個 iterator 物件。當執行 iterator 的 next 方法的時候,function* 裡面的 code 才會被執行,並停在第一個 yield expression 前。呼叫 next 並執行,會回傳一個物件,該物件會帶有一個 value 屬性,其值就是 yield 回傳的值,還會帶有一個 done 屬性,他的型態是 boolean。({value: val, done: true/false})。當該 yield expression 是最後一個的時候,done 屬性會是 true。

generator 裡面的 yield expeession 不一定就直接回傳一個值。也可以再指向另一個 generator function。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}

function* generator(i) {
yield i;
yield* anotherGenerator(i);
yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

generators 也可以帶入參數。如下面的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* logGenerator() {
console.log(0);
console.log(1, yield);
console.log(2, yield);
console.log(3, yield);
}

var gen = logGenerator();

// the first call of next executes from the start of the function
// until the first yield statement
gen.next(); // 0
gen.next('pretzel'); // 1 pretzel
gen.next('california'); // 2 california
gen.next('mayonnaise'); // 3 mayonnaise

function* 裡面有 return,在執行到該行的時候該 function* 就會結束。即使後面還有其他的 yield expression,呼叫 next 也不會再繼續執行。
當 generator 執行完,會回傳 {value: undefined, done: true}

Generators 不能作為建構函式

以下內容整理自這段影片 Generators in JavaScript

用 generator 來改寫 iterator

利用 generator 來改寫上一篇的範例中的 iterator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const dragonArmy = {
[Symbol.iterator] : function*(){
while(true){
const enoughDragonSpawned = Math.random() > 0.75;
if( enoughDrangonSpawned ) return
yield makeDragon()
}
}
// 上一篇 iterator 的寫法
// [Symbol.iterator]: () => {
// return {
// next: () => {
// const enoughDragonSpawned = Math.random() > 0.75;
// if(!enoughDragonSpawned)
// return {
// value: makeDragon(),
// done: false
// }
// return {done: true}
// }
// }
// }
}

for (const dragon of dragonArmy){
dragon //
}

generator 範例

宣告一個 someDragon 的 generator 函式來觀察 generator 的執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
funtion* someDragon(){
while(true){ // it's always true
const enoughDragonSpawned = Math.random() > 0.75;
if( enoughDrangonSpawned ) return
yield makeDragon()
}
}

const iterator = someDragons();
iterator.next() // { value: 'medium lightning dragon', done: false }
iterator.next() // { value: 'tiny lightning dragon', done: false }
iterator.next() // { value: 'big fire dragon', done: false }
iterator.next() // { value: 'undefined', done: true }
iterator.next() // { value: 'undefined', done: true }
iterator.next() // { value: 'undefined', done: true }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
funtion* someDragon(){
yield 'fluffinkins some dragon';
yield 'make the fine dragon';
if(Math.random() > 0.5) return; // 當它 return的時候 done: true,後面的程式碼都不會被執行。
yield 'hardy the dog';
return 'hardy the dog!!' // 使用 return the next iteration will not happen
}

const iterator = someDragons();
iterator.next() // { value: 'medium lightning dragon', done: false }
iterator.next() // { value: 'tiny lightning dragon', done: false }
iterator.next() // { value: 'big fire dragon', done: false }
iterator.next() // { value: 'undefined', done: true }
iterator.next() // { value: 'undefined', done: true }
iterator.next() // { value: 'undefined', done: true }

再重複一次

Calling a generator function does NOT run it …yet!

iterators 跟 generator 的關係,可以想成電視和遙控器的關係。
iterators 就像是遙控器,透過 next() 來操縱 generator 的執行。

影片的總結說到,

It’s just a SYNTX SUGAR to help create iterator.
generator 就是一個語法糖,可以更容易的建立 iterator。

不使用 generator 來重寫 someDragon 函式

透過下面的重寫,可以更清楚看出來整個流程,也可以比較出為什麼主持人會說 generator 是語法糖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function someDragons(){
let iterations = -1;
const iterator = {
next: () => {
iterations ++
if(iterations === 0)
return { value: 'fluffykins dragon', done: false };
if(iterations === 1)
return { value: 'mark the fine dragon', done: false };
if(iterations === 2){
if (Math.random > 0.5){
return {value: 'hardy the dog', done: true}
}
}
return {done: true};
}
}
return iterator
}

const iterator = someDragons();
console.log(iterator.next()) // {value: 'fluffykins dragon', done: false }
console.log(iterator.next()) // {value: 'mark the fine dragon', done: false }
console.log(iterator.next()) // { done: false }

小結

本篇主要是延續上一篇有關 iterators 來介紹 generators,至於有關 async 是怎麼建立在 generators 上,async, await f又是怎麼一回事就等之後再來繼續研究。

參考資料