一文帶你搞懂JavaScript的Generator函數(shù)
一、概念
常規(guī)函數(shù)只會返回一個單一值(或者不返回任何值)。
而 Generator 可以按需一個接一個地返回(“yield”)多個值。它們可與 iterable 完美配合使用,從而可以輕松地創(chuàng)建數(shù)據(jù)流。
二、Generator 函數(shù)
要創(chuàng)建一個 generator,需要一個特殊的語法結(jié)構(gòu):function*,即所謂的 “generator function”。
Generator 函數(shù)與常規(guī)函數(shù)的行為不同。在此類函數(shù)被調(diào)用時,它不會運(yùn)行其代碼。而是返回一個被稱為 “generator object” 的特殊對象,來管理執(zhí)行流程。
例如,可以創(chuàng)建一個 generator 并獲取其第一個產(chǎn)出的(yielded)值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}截至目前,只獲得了第一個值,現(xiàn)在函數(shù)執(zhí)行處在第二行:

讓再次調(diào)用 generator.next()。代碼恢復(fù)執(zhí)行并返回下一個 yield 的值:
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
如果第三次調(diào)用 generator.next(),代碼將會執(zhí)行到 return 語句,此時就完成這個函數(shù)的執(zhí)行:
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}運(yùn)行結(jié)果:

三、Generator 是可迭代的
當(dāng)你看到 next() 方法,或許你已經(jīng)猜到了 generator 是 可迭代(iterable)的。(譯注:next() 是 iterator 的必要方法)
可以使用 for..of 循環(huán)遍歷它所有的值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2
}運(yùn)行結(jié)果:

for..of 寫法是不是看起來比 .next().value 優(yōu)雅多了?
注:
上面這個例子會先顯示 1,然后是 2,然后就沒了。它不會顯示 3!
這是因為當(dāng) done: true 時,for..of 循環(huán)會忽略最后一個 value。因此,如果想要通過 for..of 循環(huán)顯示所有的結(jié)果,必須使用 yield 返回它們:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2,然后是 3
}因為 generator 是可迭代的,可以使用 iterator 的所有相關(guān)功能。
例如:spread 語法 ...:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3運(yùn)行結(jié)果:

四、Generator 組合
Generator 組合(composition)是 generator 的一個特殊功能,它允許透明地(transparently)將 generator 彼此“嵌入(embed)”到一起。
例如,有一個生成數(shù)字序列的函數(shù):組合的 generator 的例子:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z運(yùn)行結(jié)果:

generator.throw
上面的例子中觀察到的那樣,外部代碼可能會將一個值傳遞到 generator,作為 yield 的結(jié)果。
但是它也可以在那里發(fā)起(拋出)一個 error。這很自然。
因為 error 本身也是一種結(jié)果,要向 yield 傳遞一個 error,應(yīng)該調(diào)用 generator.throw(err)。
在這種情況下,err 將被拋到對應(yīng)的 yield 所在的那一行。
例:
"2 + 2?" 的 yield 導(dǎo)致了一個 error:
function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("The execution does not reach here, because the exception is thrown above");
} catch(e) {
alert(e); // 顯示這個 error
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("The answer is not found in my database")); // (2)運(yùn)行結(jié)果:

五、總結(jié)
本文基于JavaScript基礎(chǔ),介紹了Generator函數(shù) ,重點(diǎn)介紹了如何進(jìn)行Generator 組合,采用圖文結(jié)合的方式。采用JavaScript語言,能夠更直觀的的理解,希望能夠幫助讀者更好的學(xué)習(xí)。
歡迎大家積極嘗試,有時候看到別人實(shí)現(xiàn)起來很簡單,但是到自己動手實(shí)現(xiàn)的時候,總會有各種各樣的問題,切勿眼高手低,勤動手,才可以理解的更加深刻。
代碼很簡單,希望對你學(xué)習(xí)有幫助。


























