是 Ray 不是 Array

整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ

Advertisement
2025-12-05 JavaScript

什麼是 JavaScript 的 Generator Functions (function*)?

什麼是 JavaScript 的 Generator Functions (function*)?

前言

function* 是一個比較少人使用但卻非常強大的語法,它允許我們定義一種特殊的函式,稱為「生成器函式」(Generator Function),所以這一篇就要來介紹什麼是 Generator Functions 以及它們的用途。

Generator Functions?

Generator Functions 簡單來講是一個「可以暫停」和「可以恢復」的一個函式,那該怎麼使用呢?其實很簡單,只要在函式宣告的時候,在 function 關鍵字後面加上一個星號 * 即可。

底下是幾個定義 Generator Function 的範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 函式陳述式 - Generator Function
function* myGenerator() {
console.log("Hello");
yield; // 暫停點
console.log("World");
}

// 函式表達式 - Generator Function
const myGenerator = function* () {
console.log("Hello");
yield; // 暫停點
console.log("World");
};

這邊使用上要注意,Generator Function 並不支援直接使用箭頭函式(Arrow Function),所以我們只能使用傳統的函式宣告或函式表達式來定義 Generator Function。

為什麼需要 Generator Functions?

接下來我們要來看一些為什麼我們會需要使用 Generator Functions 的情境。

通常我們建立一個函式:

1
2
3
4
5
6
function myFunction() {
console.log("Hello");
console.log("World");
}

myFunction(); // 輸出 "Hello" 和 "World"

我們可以看到當我們呼叫了 myFunction 後,函式會從頭到尾執行完畢,無法中途暫停。

但可能我們真正需要的需求是,隨著某些條件的變化,我們希望能夠在函式執行的過程中,暫停並等待下一次的呼叫,這時候 Generator Functions 就派上用場了。

用法也非常簡單:

1
2
3
4
5
function* myGenerator() {
console.log("Hello");
yield; // 暫停點
console.log("World");
}

你只需要在你想要暫停的地方使用 yield 關鍵字,這樣當函式執行到 yield 時就會暫停,等待下一次的呼叫。

1
2
3
const gen = myGenerator(); // 建立生成器物件
gen.next(); // 輸出 "Hello"
gen.next(); // 輸出 "World"

你會發現 gen 並不會像一般函式一樣直接執行,而是回傳一個生成器物件,這個物件有一個 next 方法,每次呼叫 next 都會讓函式從上次暫停的地方繼續執行,直到遇到下一個 yield 或是函式結束。

除此之外 yield 可以搭配回傳值使用,例如 yield value,這樣當你呼叫 next 時,可以取得這個值,所以如果你是習慣撰寫 Pure Function 的開發者,那也可以透過 yield 來達到類似的效果。

1
2
3
4
5
6
7
function* myGenerator(name) {
yield `Hello, ${name}`;
yield `${name}, 你預計今天要完成什麼任務?`;
}
const gen = myGenerator("小明");
console.log(gen.next().value); // 輸出 "Hello, 小明"
console.log(gen.next().value); // 輸出 "小明, 你預計今天要完成什麼任務?"

很有趣吧?你可以隨時呼叫 next 來取得下一個 yield 的值,這樣就能讓函式的執行變得更加靈活。

另外 next 還能夠傳入一個參數,這個參數會成為上一個 yield 表達式的回傳值,這樣就能讓函式在每次恢復執行時,根據外部傳入的值來決定下一步的行為。

1
2
3
4
5
6
7
8
function* myGenerator(name) {
const task = yield `Hello, ${name}`;
console.log(task);
yield `${name}, 你預計今天要完成什麼任務? ${task}`;
}
const gen = myGenerator("小明");
console.log(gen.next().value); // 輸出 "Hello, 小明"
console.log(gen.next("寫程式").value); // 輸出 "小明, 你預計今天要完成什麼任務? 寫程式"

這裡有一個新手容易搞混的地方:yield 後面的值(Hello, ${name}) 是輸出給外部的(也就是 gen.next().value 拿到的值)。

而變數 task 接收到的,並不是這一行的 Hello...,而是下一次外部呼叫 gen.next("寫程式") 時傳進來的參數。也就是說,外部傳入的 "寫程式" 會取代掉原本暫停的 yield 表達式,賦值給 task。

那還可以在什麼情境下使用 Generator Functions 呢?另一個常見的應用場景是「非同步的 API 呼叫」,透過 Generator Functions 我們可以讓非同步的程式碼看起來像是同步的,這樣就能讓程式碼更容易閱讀和維護。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* fetchData() {
const user = yield fetch("/api/user").then(res => res.json());
console.log(user);
const posts = yield fetch(`/api/posts?userId=${user.id}`).then(res => res.json());
console.log(posts);
}

const gen = fetchData();

function handle(yielded) {
if (!yielded.done) {
yielded.value.then(data => {
handle(gen.next(data));
});
}
}
handle(gen.next());

又或者是那種滾動式加載(infinite scrolling)的應用場景,這也可以使用 Generator Functions 來實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
function* infiniteScroll() {
let page = 1;
while (true) {
// 實際應用中,通常會判斷是否還有下一頁,如果沒有就 break
const data = yield fetch(`/api/data?page=${page}`).then(res => res.json());
if (data.length === 0) break; // 如果沒資料就停止
console.log(data);
page++;
}
}
const gen = infiniteScroll();

gen.next().value.then((data) => ...); // 載入第一頁

Generator Functions 冷知識

嚴格來說,我們常用的 async/await 其實就是 Generator Functions + Promise + 自動執行器(Auto Runner) 的語法糖。

你剛剛在上面看到的那個 handle 函式,其實就是一個簡易版的自動執行器(類似早期的 co 套件原理)。

async/await 的出現,就是為了幫我們自動處理這些 generator.next() 和 Promise 的串接,讓我們不用自己手寫遞迴,就能優雅地等待非同步結果。

如果你將前面的非同步 API 呼叫的範例改寫成 async/await 的話,就會變成我們熟悉的樣子:

1
2
3
4
5
6
7
8
async function fetchData() {
const user = await fetch("/api/user").then(res => res.json());
console.log(user);
const posts = await fetch(`/api/posts?userId=${user.id}`).then(res => res.json());
console.log(posts);
}

fetchData();

只是缺點你就無法像 Generator Functions 那樣,可以隨時暫停和恢復執行,因此如果你有需要這樣的功能,Generator Functions 會是一個不錯的選擇。

雖然現在我們有很方便的 ``async/await`,但在處理記憶體優化(如無限序列)或是複雜的狀態控制(如 Redux-Saga)時,Generator Functions 仍然是無可替代的強大工具哩~

整理這些技術筆記真的很花時間,如果你願意 關閉 Adblock 支持我,我會把這份感謝轉換成更多「踩坑轉避坑」的內容給你!ヽ(・∀・)ノ

Advertisement

你的支持會直接轉換成更多技術筆記

如果我的筆記讓你少踩一個坑、節省 Debug 的時間,
也許你可以請我喝杯咖啡,讓我繼續當個不是 Array 的 Ray ☕

buymeacoffee | line | portaly

Terminal

分享這篇文章

留言

© 2025 Ray. All rights reserved.

Powered by Ray Theme