轉(zhuǎn)載:ES6 Generator函數(shù)

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方法压鉴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锻拘,隨后出現(xiàn)的幾起案子晴弃,更是在濱河造成了極大的恐慌,老刑警劉巖逊拍,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件上鞠,死亡現(xiàn)場離奇詭異,居然都是意外死亡芯丧,警方通過查閱死者的電腦和手機芍阎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缨恒,“玉大人谴咸,你說我怎么就攤上這事∑叮” “怎么了岭佳?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萧锉。 經(jīng)常有香客問我珊随,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任叶洞,我火速辦了婚禮鲫凶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衩辟。我一直安慰自己螟炫,他們只是感情好,可當我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布艺晴。 她就那樣靜靜地躺著昼钻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪封寞。 梳的紋絲不亂的頭發(fā)上换吧,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機與錄音钥星,去河邊找鬼。 笑死满着,一個胖子當著我的面吹牛谦炒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播风喇,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宁改,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了魂莫?” 一聲冷哼從身側(cè)響起还蹲,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耙考,沒想到半個月后谜喊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡倦始,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年斗遏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞋邑。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡诵次,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出枚碗,到底是詐尸還是另有隱情逾一,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布肮雨,位于F島的核電站遵堵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏怨规。R本人自食惡果不足惜鄙早,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一汪茧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧限番,春花似錦舱污、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霜瘪,卻和暖如春珠插,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背颖对。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工捻撑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缤底。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓顾患,卻偏偏與公主長得像,于是被迫代替她去往敵國和親个唧。 傳聞我的和親對象是個殘疾皇子江解,可洞房花燭夜當晚...
    茶點故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 在此處先列下本篇文章的主要內(nèi)容 簡介 next方法的參數(shù) for...of循環(huán) Generator.prototy...
    醉生夢死閱讀 1,451評論 3 8
  • 簡介 基本概念 Generator函數(shù)是ES6提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同徙歼。本章詳細介紹...
    呼呼哥閱讀 1,076評論 0 4
  • 寫在前面的話犁河,說真的ES6的每一章節(jié)都超級長啊,這一塊的學習我用了兩天時間才完成魄梯。如果不趕緊把總結(jié)寫出來桨螺,我過一段...
    雨飛飛雨閱讀 470評論 0 2
  • 本文作者就是我,簡書的microkof酿秸。如果您覺得本文對您的工作有意義彭谁,產(chǎn)生了不可估量的價值,那么請您不吝打賞我允扇,...
    microkof閱讀 23,735評論 16 78
  • 清晨缠局,呼吸清新的空氣,感受清新的第一縷陽光考润,心情也是清新的O猎啊!在這清新的早晨糊治,開始清新的工作3!。绎谦。管闷。
    葉子藍藍閱讀 136評論 0 0