理解 JavaScript Generator 函式與 yield 運作原理
此文章是 FrontendMaster 上的 Advanced Web Development Quiz 課程筆記
問題
When does In log: My input! get logged?
Select the correct answer.
function* generatorFunction() {
const result = yield "My input!";
console.log("In log:", result);
return "All done!";
}
const it = generatorFunction();[1] it.next()
[2] it.next("My input!"), it.next()
[3] it.next(), it.next("My input!")
[4] it.next(), it.next()JavaScript Generator
Generator 是個特別的函式,相較於一般函式執行後只能回傳一次值,Generator 透過 yield 可以回傳多個值。建立 Generator 的方式就是加上 *,如:
javascript
function* generatorFunction() {
// ...
}當 Generator 函式被呼叫的時候,function body 不會直接被執行,而是回傳 generator object。
javascript
function* generatorFunction() {
// ...
}
console.log(generatorFunction()); // Object [Generator] {}接下來可以透過呼叫 next 方法來觸發執行,此時 function 會開始執行,直到完成執行出現 yield 的那一行並停止且回傳一個物件 (例如:{value: 1, done: false} )。 接續執行 next 之後就會繼續從上一次停止的位置執行。如果執行完畢或是遇到 return 此時回傳的物件中的 done 就會變成 true。
javascript
function* generatorFunction() {
yield 1;
yield 2;
return 3;
}
const generator = generatorFunction();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.next()); // {value: 2, done: false}
console.log(generator.next()); // {value: 3, done: true}透過 next 傳值
當我們在 next() 方法中帶入參數時,這個參數會成為上一個 yield 表達式的回傳值。
javascript
function* generatorFunction() {
const result = yield 'hello';
console.log('Log: ' + result);
yield 2;
}
const generator = generatorFunction();
console.log(generator.next());
// {value: "hello", done: false}
console.log(generator.next('YO!'));
// "Log: YO!"
// {value: 2, done: false}注意:第一次呼叫
next()時傳入的參數會被忽略,因為此時還沒有對應的 yield 表達式來接收這個值。
搭配 try...catch...finally
generator必須要有執行過 body 內的程式 (執行過next),才執行return或throw,這樣子才會去執行catch或finally內的程式
我們也可以透過 generator.return 直接讓這個 generator 直接結束,這可以跟 try...finally 一起使用
javascript
function* generatorFunction() {
try {
yield 1;
yield 2;
} finally {
yield 3;
console.log('finally');
}
}
const generator = generatorFunction();
console.log(generator.next());
// { value: 1, done: false }
console.log(generator.return());
// { value: 3, done: false }
console.log(generator.next());
// finally
// { value: undefined, done: false }使用 try...catch 搭配 generator.throw,就可以做到更細緻的錯誤控制
javascript
function* generatorFunction() {
try {
yield 1;
yield 2;
} catch (e) {
alert(e);
}
}
const generator = generatorFunction();
console.log(generator.next());
// { value: 1, done: false }
console.log(generator.throw(new Error('Something went wrong!')));
// alert !
// { value: undefined, done: false }generator 可以被迭代
我們可以透過 for...of 來對 generator 跑迴圈。
javascript
function* generatorFunction() {
yield 1;
yield 2;
return 3;
}
const generator = generatorFunction();
for (value of generator) {
console.log(value); // 1 , 2
}因為
for...of只會抓 yield 的值,所以最後不會印出 3,需要這個值的話應該要改成使用yield
實例
我從來沒有實際使用過這個功能,不過似乎可以用來生成 runtime 期間的 id
javascript
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const generator = idGenerator();
console.log(generator.next()); // 1
console.log(generator.next()); // 2
console.log(generator.next()); // 3答案
[3] it.next(), it.next("My input!")