快速入門 generator

一個有點邊緣的東西,但學起來還不錯。

簡述

最近想要學習 redux saga 這個東西,如果你聽過這東西的話就應該也知道學 saga 會需要一些 generator 的前備知識。

總之,為了讓接下來學習 redux saga 能夠順利,決定來稍微認識一下這個東西。

什麼是 generator?

我覺得用範例來解釋最快,所以先來看段 code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* myGenerator() {
for (let i = 1; i <= 3; i++) {
yield i
}
}

// 回傳值是一個迭代器
// 但比起這個說法,我覺得「可以暫停的 function」 是一個更好理解的方式。
const iterable = myGenerator()
console.log(iterable.next()) // { value: 1, done: false }
console.log(iterable.next()) // { value: 2, done: false }
console.log(iterable.next()) // { value: 3, done: false }
console.log(iterable.next()) // { value: undefined, done: true }
console.log(iterable.next()) // { value: undefined, done: true }
console.log(iterable.next()) // { value: undefined, done: true }

使用 generator 的方式就是在 function 後面加上一個 *,雖然也可以加在名稱前面,但我還是偏好加在 function 後面。

接著執行這個 function 時,跟一般的 function 有個最大的不同,就是它不會立即執行每一圈迴圈,跑到迴圈結束為止,而是會被「暫停住」,然後回傳一個 iterable

至於什麼是 iterable?簡單來說,你只要想成每一次我 call iterable.next() 時,它就會找到下一個 yield 的位置,繼續往下執行,直到又碰到下一個 yield 為止。

因為我每一次都是在迭代 yield 這個關鍵字,所以才會說是「iterable(可迭代的)」

另外 next() 有回傳值,內容長這樣:

1
2
3
4
5
// typescript
type res = {
value: string
done: boolean
}

以剛剛的例子來說你大概就猜的出來用意了,value 就是 yield 後面接的值,而 done 則是用來表示「目前有沒有 yield?」

順道一提,如果 generator 有 return 回傳值的話,結果就會變這樣:

1
2
3
4
5
6
7
8
9
10
11
12
function* myGenerator() {
for (i = 1; i <= 3; i++) {
yield i
}
return i
}

const iterable = myGenerator()
console.log(iterable.next()) // { value: 1, done: false }
console.log(iterable.next()) // { value: 2, done: false }
console.log(iterable.next()) // { value: 3, done: false }
console.log(iterable.next()) // { value: 4, done: true }

算是一個特例,可以讓你在 done: true 的時候還能拿到 value,不過一般不會這樣用,這邊只是順便提一下。

這就是關於 generator 的最最最基本的用法。

在 next 中傳入參數

這是另一個在 generator 中很重要的觀念,建議一定要弄懂。

簡單來說,在 next 傳入的參數可以「改寫上一個 yield 的值」,一樣來看例子:

1
2
3
4
5
6
7
8
9
10
11
function* myGenerator(x) {
const y = 2 * (yield x + 1)
const z = yield y / 3
return x + y + z
}

const iterable = myGenerator(5)

console.log(iterable.next().value)
console.log(iterable.next(12).value)
console.log(iterable.next(13).value)

看起來有夠複雜,但只要照著 code 來一步一步來執行就會好懂一些了:

iterable.next()

因為這是第一個 yield,所以不傳入參數,5 + 1 得到 6

iterable.next(12)

依照剛剛說的,把上一個 yield 後面的值全部改寫,所以就會從 (x + 1) 變成 12

接著 const y = 2 * 12 得到 24,所以目前的 yield 後面的值就會是 824 / 3)。

iterable.next(13)

一樣把上一個 yield 後面的值全部改寫,所以就會從 y / 3 變成 13

最後這次因為是 return,所以 5 + 24 + 13 得到 42

如果有點難懂的話,可以想成這樣:

1
2
3
4
5
function* myGenerator (5) {
const y = 2 * (yield (12));
const z = yield (13);
return 5 + 24 + 13;
}

for … of

iteralbe 可以用 for...of 來遍歷 yield 的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* myGenerator() {
yield 1
yield 4
yield 10
}

const iterable = myGenerator()

for (const x of iterable) {
// 1
// 4
// 10
console.log(x)
}

總之呢,我覺得最重要的是一定要分清楚 next()next(param) 的行為有什麼不同,目前關於 generator 的部分先知道這些就差不多了,剩下的有時間再去研究就好。

最後,如果你想試看看用 generator 來實作「非同步」操作的話,可以參考:從 callback 到 Promise 再到 generator

參考資料

從 Callback 到 Promise 再到 Generator 用實作的方式來重新學習 Redux
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×