1.基本概念
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案尽棕,語法行為與傳統(tǒng)函數(shù)完全不同。本章詳細介紹 Generator 函數(shù)的語法和 API廓啊,它的異步編程應用請看《Generator 函數(shù)的異步應用》一章甸祭。
Generator 函數(shù)有多種理解角度呢蔫。語法上恕曲,首先可以把它理解成,Generator 函數(shù)是一個狀態(tài)機渤涌,封裝了多個內(nèi)部狀態(tài)佩谣。
執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象,也就是說实蓬,Generator 函數(shù)除了狀態(tài)機茸俭,還是一個遍歷器對象生成函數(shù)。返回的遍歷器對象安皱,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個狀態(tài)调鬓。
形式上,Generator 函數(shù)是一個普通函數(shù)酌伊,但是有兩個特征腾窝。一是,function關(guān)鍵字與函數(shù)名之間有一個星號居砖;二是虹脯,函數(shù)體內(nèi)部使用yield表達式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)奏候。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代碼定義了一個 Generator 函數(shù)helloWorldGenerator循集,它內(nèi)部有兩個yield表達式(hello和world),即該函數(shù)有三個狀態(tài):hello蔗草,world 和 return 語句(結(jié)束執(zhí)行)咒彤。
然后疆柔,Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對圓括號镶柱。不同的是旷档,調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行奸例,返回的也不是函數(shù)運行結(jié)果彬犯,而是一個指向內(nèi)部狀態(tài)的指針對象,也就是上一章介紹的遍歷器對象(Iterator Object)查吊。
下一步谐区,必須調(diào)用遍歷器對象的next方法,使得指針移向下一個狀態(tài)逻卖。也就是說宋列,每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行评也,直到遇到下一個yield表達式(或return語句)為止炼杖。換言之,Generator 函數(shù)是分段執(zhí)行的盗迟,yield表達式是暫停執(zhí)行的標記坤邪,而next方法可以恢復執(zhí)行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
上面代碼一共調(diào)用了四次next方法罚缕。
第一次調(diào)用艇纺,Generator 函數(shù)開始執(zhí)行,直到遇到第一個yield表達式為止邮弹。next方法返回一個對象黔衡,它的value屬性就是當前yield表達式的值hello,done屬性的值false腌乡,表示遍歷還沒有結(jié)束盟劫。
第二次調(diào)用,Generator 函數(shù)從上次yield表達式停下的地方与纽,一直執(zhí)行到下一個yield表達式侣签。next方法返回的對象的value屬性就是當前yield表達式的值world,done屬性的值false渣锦,表示遍歷還沒有結(jié)束硝岗。
第三次調(diào)用,Generator 函數(shù)從上次yield表達式停下的地方袋毙,一直執(zhí)行到return語句(如果沒有return語句型檀,就執(zhí)行到函數(shù)結(jié)束)。next方法返回的對象的value屬性听盖,就是緊跟在return語句后面的表達式的值(如果沒有return語句胀溺,則value屬性的值為undefined)裂七,done屬性的值true,表示遍歷已經(jīng)結(jié)束仓坞。
第四次調(diào)用背零,此時 Generator 函數(shù)已經(jīng)運行完畢,next方法返回對象的value屬性為undefined无埃,done屬性為true徙瓶。以后再調(diào)用next方法,返回的都是這個值嫉称。
總結(jié)一下侦镇,調(diào)用 Generator 函數(shù),返回一個遍歷器對象织阅,代表 Generator 函數(shù)的內(nèi)部指針壳繁。以后,每次調(diào)用遍歷器對象的next方法荔棉,就會返回一個有著value和done兩個屬性的對象闹炉。value屬性表示當前的內(nèi)部狀態(tài)的值,是yield表達式后面那個表達式的值润樱;done屬性是一個布爾值渣触,表示是否遍歷結(jié)束。
ES6 沒有規(guī)定壹若,function關(guān)鍵字與函數(shù)名之間的星號昵观,寫在哪個位置。這導致下面的寫法都能通過舌稀。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
由于 Generator 函數(shù)仍然是普通函數(shù),所以一般的寫法是上面的第三種灼擂,即星號緊跟在function關(guān)鍵字后面壁查。本書也采用這種寫法。
yield 表達式
由于 Generator 函數(shù)返回的遍歷器對象剔应,只有調(diào)用next方法才會遍歷下一個內(nèi)部狀態(tài)睡腿,所以其實提供了一種可以暫停執(zhí)行的函數(shù)。yield表達式就是暫停標志峻贮。
遍歷器對象的next方法的運行邏輯如下席怪。
(1)遇到y(tǒng)ield表達式,就暫停執(zhí)行后面的操作纤控,并將緊跟在yield后面的那個表達式的值挂捻,作為返回的對象的value屬性值。
(2)下一次調(diào)用next方法時船万,再繼續(xù)往下執(zhí)行刻撒,直到遇到下一個yield表達式骨田。
(3)如果沒有再遇到新的yield表達式,就一直運行到函數(shù)結(jié)束声怔,直到return語句為止态贤,并將return語句后面的表達式的值,作為返回的對象的value屬性值醋火。
(4)如果該函數(shù)沒有return語句悠汽,則返回的對象的value屬性值為undefined。
需要注意的是芥驳,yield表達式后面的表達式柿冲,只有當調(diào)用next方法、內(nèi)部指針指向該語句時才會執(zhí)行晚树,因此等于為 JavaScript 提供了手動的“惰性求值”(Lazy Evaluation)的語法功能姻采。
function* gen() {
yield 123 + 456;
}
上面代碼中,yield后面的表達式123 + 456爵憎,不會立即求值慨亲,只會在next方法將指針移到這一句時,才會求值宝鼓。
yield表達式與return語句既有相似之處刑棵,也有區(qū)別。相似之處在于愚铡,都能返回緊跟在語句后面的那個表達式的值蛉签。區(qū)別在于每次遇到y(tǒng)ield,函數(shù)暫停執(zhí)行沥寥,下一次再從該位置繼續(xù)向后執(zhí)行碍舍,而return語句不具備位置記憶的功能。一個函數(shù)里面邑雅,只能執(zhí)行一次(或者說一個)return語句片橡,但是可以執(zhí)行多次(或者說多個)yield表達式。正常函數(shù)只能返回一個值淮野,因為只能執(zhí)行一次return捧书;Generator 函數(shù)可以返回一系列的值,因為可以有任意多個yield骤星。從另一個角度看经瓷,也可以說 Generator 生成了一系列的值,這也就是它的名稱的來歷(英語中洞难,generator 這個詞是“生成器”的意思)舆吮。
Generator 函數(shù)可以不用yield表達式,這時就變成了一個單純的暫緩執(zhí)行函數(shù)。
function* f() {
console.log('執(zhí)行了歪泳!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
上面代碼中萝勤,函數(shù)f如果是普通函數(shù),在為變量generator賦值時就會執(zhí)行呐伞。但是敌卓,函數(shù)f是一個 Generator 函數(shù),就變成只有調(diào)用next方法時伶氢,函數(shù)f才會執(zhí)行趟径。
另外需要注意,yield表達式只能用在 Generator 函數(shù)里面癣防,用在其他地方都會報錯蜗巧。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
上面代碼在一個普通函數(shù)中使用yield表達式,結(jié)果產(chǎn)生一個句法錯誤蕾盯。
下面是另外一個例子幕屹。
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
a.forEach(function (item) {
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
});
};
for (var f of flat(arr)){
console.log(f);
}
上面代碼也會產(chǎn)生句法錯誤,因為forEach方法的參數(shù)是一個普通函數(shù)级遭,但是在里面使用了yield表達式(這個函數(shù)里面還使用了yield*表達式望拖,詳細介紹見后文)。一種修改方法是改用for循環(huán)挫鸽。
var arr = [1, [[2, 3], 4], [5, 6]];
var flat = function* (a) {
var length = a.length;
for (var i = 0; i < length; i++) {
var item = a[i];
if (typeof item !== 'number') {
yield* flat(item);
} else {
yield item;
}
}
};
for (var f of flat(arr)) {
console.log(f);
}
// 1, 2, 3, 4, 5, 6
另外说敏,yield表達式如果用在另一個表達式之中,必須放在圓括號里面丢郊。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield表達式用作函數(shù)參數(shù)或放在賦值表達式的右邊盔沫,可以不加括號。
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
2.next 方法的參數(shù)
yield表達式本身沒有返回值枫匾,或者說總是返回undefined架诞。next方法可以帶一個參數(shù),該參數(shù)就會被當作上一個yield表達式的返回值干茉。
function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
上面代碼先定義了一個可以無限運行的 Generator 函數(shù)f侈贷,如果next方法沒有參數(shù),每次運行到y(tǒng)ield表達式等脂,變量reset的值總是undefined。當next方法帶一個參數(shù)true時撑蚌,變量reset就被重置為這個參數(shù)(即true)上遥,因此i會等于-1,下一輪循環(huán)就會從-1開始遞增争涌。
這個功能有很重要的語法意義粉楚。Generator 函數(shù)從暫停狀態(tài)到恢復運行,它的上下文狀態(tài)(context)是不變的。通過next方法的參數(shù)模软,就有辦法在 Generator 函數(shù)開始運行之后伟骨,繼續(xù)向函數(shù)體內(nèi)部注入值。也就是說燃异,可以在 Generator 函數(shù)運行的不同階段携狭,從外部向內(nèi)部注入不同的值,從而調(diào)整函數(shù)行為回俐。
再看一個例子逛腿。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
上面代碼中,第二次運行next方法的時候不帶參數(shù)仅颇,導致 y 的值等于2 * undefined(即NaN)单默,除以 3 以后還是NaN,因此返回對象的value屬性也等于NaN忘瓦。第三次運行Next方法的時候不帶參數(shù)搁廓,所以z等于undefined,返回對象的value屬性等于5 + NaN + undefined耕皮,即NaN境蜕。
如果向next方法提供參數(shù),返回結(jié)果就完全不一樣了明场。上面代碼第一次調(diào)用b的next方法時汽摹,返回x+1的值6;第二次調(diào)用next方法苦锨,將上一次yield表達式的值設(shè)為12逼泣,因此y等于24,返回y / 3的值8舟舒;第三次調(diào)用next方法拉庶,將上一次yield表達式的值設(shè)為13,因此z等于13秃励,這時x等于5氏仗,y等于24,所以return語句的值等于42夺鲜。
注意皆尔,由于next方法的參數(shù)表示上一個yield表達式的返回值,所以在第一次使用next方法時币励,傳遞參數(shù)是無效的慷蠕。V8 引擎直接忽略第一次使用next方法時的參數(shù),只有從第二次使用next方法開始食呻,參數(shù)才是有效的流炕。從語義上講觉既,第一個next方法用來啟動遍歷器對象悴侵,所以不用帶有參數(shù)。
再看一個通過next方法的參數(shù),向 Generator 函數(shù)內(nèi)部輸入值的例子诊沪。
function* dataConsumer() {
console.log('Started');
console.log(`1. ${yield}`);
console.log(`2. ${yield}`);
return 'result';
}
let genObj = dataConsumer();
genObj.next();
// Started
genObj.next('a')
// 1. a
genObj.next('b')
// 2. b
上面代碼是一個很直觀的例子哥攘,每次通過next方法向 Generator 函數(shù)輸入值虱颗,然后打印出來罗丰。
如果想要第一次調(diào)用next方法時,就能夠輸入值峻堰,可以在 Generator 函數(shù)外面再包一層讹开。
function wrapper(generatorFunction) {
return function (...args) {
let generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
console.log(`First input: ${yield}`);
return 'DONE';
});
wrapped().next('hello!')
// First input: hello!
上面代碼中,Generator 函數(shù)如果不用wrapper先包一層捐名,是無法第一次調(diào)用next方法旦万,就輸入?yún)?shù)的。
3.for...of 循環(huán)
for...of循環(huán)可以自動遍歷 Generator 函數(shù)時生成的Iterator對象镶蹋,且此時不再需要調(diào)用next方法成艘。
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
上面代碼使用for...of循環(huán),依次顯示 5 個yield表達式的值贺归。這里需要注意淆两,一旦next方法的返回對象的done屬性為true,for...of循環(huán)就會中止拂酣,且不包含該返回對象秋冰,所以上面代碼的return語句返回的6,不包括在for...of循環(huán)之中婶熬。
下面是一個利用 Generator 函數(shù)和for...of循環(huán)剑勾,實現(xiàn)斐波那契數(shù)列的例子。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
從上面代碼可見赵颅,使用for...of語句時不需要使用next方法虽另。
利用for...of循環(huán),可以寫出遍歷任意對象(object)的方法饺谬。原生的 JavaScript 對象沒有遍歷接口捂刺,無法使用for...of循環(huán),通過 Generator 函數(shù)為它加上這個接口募寨,就可以用了族展。
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
上面代碼中,對象jane原生不具備 Iterator 接口拔鹰,無法用for...of遍歷仪缸。這時,我們通過 Generator 函數(shù)objectEntries為它加上遍歷器接口格郁,就可以用for...of遍歷了。加上遍歷器接口的另一種寫法是,將 Generator 函數(shù)加到對象的Symbol.iterator屬性上面例书。
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe
除了for...of循環(huán)以外锣尉,擴展運算符(...)、解構(gòu)賦值和Array.from方法內(nèi)部調(diào)用的决采,都是遍歷器接口自沧。這意味著,它們都可以將 Generator 函數(shù)返回的 Iterator 對象树瞭,作為參數(shù)拇厢。
function* numbers () {
yield 1
yield 2
return 3
yield 4
}
// 擴展運算符
[...numbers()] // [1, 2]
// Array.from 方法
Array.from(numbers()) // [1, 2]
// 解構(gòu)賦值
let [x, y] = numbers();
x // 1
y // 2
// for...of 循環(huán)
for (let n of numbers()) {
console.log(n)
}
// 1
// 2
4.Generator.prototype.throw()
Generator 函數(shù)返回的遍歷器對象,都有一個throw方法晒喷,可以在函數(shù)體外拋出錯誤孝偎,然后在 Generator 函數(shù)體內(nèi)捕獲。
var g = function* () {
try {
yield;
} catch (e) {
console.log('內(nèi)部捕獲', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕獲', e);
}
// 內(nèi)部捕獲 a
// 外部捕獲 b
上面代碼中凉敲,遍歷器對象i連續(xù)拋出兩個錯誤衣盾。第一個錯誤被 Generator 函數(shù)體內(nèi)的catch語句捕獲。i第二次拋出錯誤爷抓,由于 Generator 函數(shù)內(nèi)部的catch語句已經(jīng)執(zhí)行過了势决,不會再捕捉到這個錯誤了,所以這個錯誤就被拋出了 Generator 函數(shù)體蓝撇,被函數(shù)體外的catch語句捕獲果复。
throw方法可以接受一個參數(shù),該參數(shù)會被catch語句接收渤昌,建議拋出Error對象的實例虽抄。
var g = function* () {
try {
yield;
} catch (e) {
console.log(e);
}
};
var i = g();
i.next();
i.throw(new Error('出錯了!'));
// Error: 出錯了耘沼!(…)
注意极颓,不要混淆遍歷器對象的throw方法和全局的throw命令。上面代碼的錯誤群嗤,是用遍歷器對象的throw方法拋出的菠隆,而不是用throw命令拋出的。后者只能被函數(shù)體外的catch語句捕獲狂秘。
var g = function* () {
while (true) {
try {
yield;
} catch (e) {
if (e != 'a') throw e;
console.log('內(nèi)部捕獲', e);
}
}
};
var i = g();
i.next();
try {
throw new Error('a');
throw new Error('b');
} catch (e) {
console.log('外部捕獲', e);
}
// 外部捕獲 [Error: a]
上面代碼之所以只捕獲了a骇径,是因為函數(shù)體外的catch語句塊,捕獲了拋出的a錯誤以后者春,就不會再繼續(xù)try代碼塊里面剩余的語句了破衔。
如果 Generator 函數(shù)內(nèi)部沒有部署try...catch代碼塊,那么throw方法拋出的錯誤钱烟,將被外部try...catch代碼塊捕獲晰筛。
var g = function* () {
while (true) {
yield;
console.log('內(nèi)部捕獲', e);
}
};
var i = g();
i.next();
try {
i.throw('a');
i.throw('b');
} catch (e) {
console.log('外部捕獲', e);
}
// 外部捕獲 a
上面代碼中嫡丙,Generator 函數(shù)g內(nèi)部沒有部署try...catch代碼塊,所以拋出的錯誤直接被外部catch代碼塊捕獲读第。
如果 Generator 函數(shù)內(nèi)部和外部曙博,都沒有部署try...catch代碼塊,那么程序?qū)箦e怜瞒,直接中斷執(zhí)行父泳。
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
g.throw();
// hello
// Uncaught undefined
上面代碼中,g.throw拋出錯誤以后吴汪,沒有任何try...catch代碼塊可以捕獲這個錯誤惠窄,導致程序報錯,中斷執(zhí)行漾橙。
throw方法拋出的錯誤要被內(nèi)部捕獲杆融,前提是必須至少執(zhí)行過一次next方法。
function* gen() {
try {
yield 1;
} catch (e) {
console.log('內(nèi)部捕獲');
}
}
var g = gen();
g.throw(1);
// Uncaught 1
上面代碼中近刘,g.throw(1)執(zhí)行時擒贸,next方法一次都沒有執(zhí)行過。這時觉渴,拋出的錯誤不會被內(nèi)部捕獲介劫,而是直接在外部拋出,導致程序出錯案淋。這種行為其實很好理解座韵,因為第一次執(zhí)行next方法,等同于啟動執(zhí)行 Generator 函數(shù)的內(nèi)部代碼踢京,否則 Generator 函數(shù)還沒有開始執(zhí)行誉碴,這時throw方法拋錯只可能拋出在函數(shù)外部。
throw方法被捕獲以后瓣距,會附帶執(zhí)行下一條yield表達式黔帕。也就是說,會附帶執(zhí)行一次next方法蹈丸。
var gen = function* gen(){
try {
yield console.log('a');
} catch (e) {
// ...
}
yield console.log('b');
yield console.log('c');
}
var g = gen();
g.next() // a
g.throw() // b
g.next() // c
上面代碼中成黄,g.throw方法被捕獲以后,自動執(zhí)行了一次next方法逻杖,所以會打印b奋岁。另外,也可以看到荸百,只要 Generator 函數(shù)內(nèi)部部署了try...catch代碼塊闻伶,那么遍歷器的throw方法拋出的錯誤,不影響下一次遍歷够话。
另外蓝翰,throw命令與g.throw方法是無關(guān)的光绕,兩者互不影響。
var gen = function* gen(){
yield console.log('hello');
yield console.log('world');
}
var g = gen();
g.next();
try {
throw new Error();
} catch (e) {
g.next();
}
// hello
// world
上面代碼中畜份,throw命令拋出的錯誤不會影響到遍歷器的狀態(tài)奇钞,所以兩次執(zhí)行next方法,都進行了正確的操作漂坏。
這種函數(shù)體內(nèi)捕獲錯誤的機制,大大方便了對錯誤的處理媒至。多個yield表達式顶别,可以只用一個try...catch代碼塊來捕獲錯誤。如果使用回調(diào)函數(shù)的寫法拒啰,想要捕獲多個錯誤驯绎,就不得不為每個函數(shù)內(nèi)部寫一個錯誤處理語句,現(xiàn)在只在 Generator 函數(shù)內(nèi)部寫一次catch語句就可以了谋旦。
Generator 函數(shù)體外拋出的錯誤剩失,可以在函數(shù)體內(nèi)捕獲;反過來册着,Generator 函數(shù)體內(nèi)拋出的錯誤拴孤,也可以被函數(shù)體外的catch捕獲。
function* foo() {
var x = yield 3;
var y = x.toUpperCase();
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next(42);
} catch (err) {
console.log(err);
}
上面代碼中甲捏,第二個next方法向函數(shù)體內(nèi)傳入一個參數(shù) 42演熟,數(shù)值是沒有toUpperCase方法的,所以會拋出一個 TypeError 錯誤司顿,被函數(shù)體外的catch捕獲芒粹。
一旦 Generator 執(zhí)行過程中拋出錯誤,且沒有被內(nèi)部捕獲大溜,就不會再執(zhí)行下去了化漆。如果此后還調(diào)用next方法,將返回一個value屬性等于undefined钦奋、done屬性等于true的對象座云,即 JavaScript 引擎認為這個 Generator 已經(jīng)運行結(jié)束了。
function* g() {
yield 1;
console.log('throwing an exception');
throw new Error('generator broke!');
yield 2;
yield 3;
}
function log(generator) {
var v;
console.log('starting generator');
try {
v = generator.next();
console.log('第一次運行next方法', v);
} catch (err) {
console.log('捕捉錯誤', v);
}
try {
v = generator.next();
console.log('第二次運行next方法', v);
} catch (err) {
console.log('捕捉錯誤', v);
}
try {
v = generator.next();
console.log('第三次運行next方法', v);
} catch (err) {
console.log('捕捉錯誤', v);
}
console.log('caller done');
}
log(g());
// starting generator
// 第一次運行next方法 { value: 1, done: false }
// throwing an exception
// 捕捉錯誤 { value: 1, done: false }
// 第三次運行next方法 { value: undefined, done: true }
// caller done
上面代碼一共三次運行next方法锨苏,第二次運行的時候會拋出錯誤疙教,然后第三次運行的時候,Generator 函數(shù)就已經(jīng)結(jié)束了伞租,不再執(zhí)行下去了贞谓。
5.Generator.prototype.return()
Generator 函數(shù)返回的遍歷器對象,還有一個return方法葵诈,可以返回給定的值裸弦,并且終結(jié)遍歷 Generator 函數(shù)祟同。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
上面代碼中,遍歷器對象g調(diào)用return方法后理疙,返回值的value屬性就是return方法的參數(shù)foo晕城。并且,Generator 函數(shù)的遍歷就終止了窖贤,返回值的done屬性為true砖顷,以后再調(diào)用next方法,done屬性總是返回true赃梧。
如果return方法調(diào)用時滤蝠,不提供參數(shù),則返回值的value屬性為undefined授嘀。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
如果 Generator 函數(shù)內(nèi)部有try...finally代碼塊物咳,那么return方法會推遲到finally代碼塊執(zhí)行完再執(zhí)行。
function* numbers () {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
上面代碼中蹄皱,調(diào)用return方法后览闰,就開始執(zhí)行finally代碼塊,然后等到finally代碼塊執(zhí)行完巷折,再執(zhí)行return方法压鉴。