深入理解Generator(译文)

原文地址https://davidwalsh.name/es6-generators-dive
本文为generator系列文章的第二篇。

如果你依然不熟悉ES6的generator,建议回去阅读系列文章的第一篇。如果您已经熟悉Generator基础用法,接下来可以开始深入去了解一些细节部分。

错误处理

Generator函数中有一个非常棒的设计,就是在generator内部代码是同步的,无论外部是同步还是异步调用的。

既然内部代码是同步的,那我们就可以在generator内部使用try...catch去处理错误。

function *foo() {
    try {
        var x = yield 3;
        console.log( "x: " + x ); // may never get here!
    }
    catch (err) {
        console.log( "Error: " + err );
    }
}

虽然这个函数会在yield 3这个语句停下来,并且停多久都可以,但是如果一个错误被传入generator函数,try...catch语句还是会捕获到这个错误。尝试用普通的异步方式去处理,例如异步函数。

但是,怎么将一个错误传入这个generator中呢?

var it = foo();
var res = it.next(); // { value:3, done:false}

// instead of resuming normally with another `next(..)` call,
// let's throw a wrench (an error) into the gears:
it.throw( "Oops!" ); // Error: Oops!

在上面的例子中,你可以看到我们使用了iterator的另外一个方法throw(..),这个方法可以将将错误抛入generator函数,就像这个错误是发生在generator函数在执行yield语句停止的那个位置。此时try...catch就会想预期那样的捕获这个错误。

Note:如果你调用了throw(..)方法,但是没有用try..catch去捕获,这个错误会像在普通函数里一样被外部捕获,所以

function *foo() { }

var it = foo();
try {
    it.throw( "Oops!" );
}
catch (err) {
    console.log( "Error: " + err ); // Error: Oops!
}

内部自身错误也是如此

function *foo() {
    var x = yield 3;
    var y = x.toUpperCase(); // could be a TypeError error!
    yield y;
}

var it = foo();

it.next(); // { value:3, done:false }

try {
    it.next( 42 ); // `42` won't have `toUpperCase()`
}
catch (err) {
    console.log( err ); // TypeError (from `toUpperCase()` call)
}

委派generator(Delegating Generators)

你也许会想要在generator函数内部调用另外一个generator函数。我并不是指用常规方法实例化一个generator,而是将你的迭代控制权委托给另外一个generator函数,为了这样做,我们使用yield语句的另外一种形式yield *
Example

function * foo() {
  yield 3;
  yield 4;
}

function *bar() {
  yield 1;
  yield 2;
  yield *foo(); // 'yield *' 将迭代控制权交给foo()
  yield 5;
}

for ( var v of bar()) {
  console.log(v)
}
// 1 2 3 4 5

让我们来研究一下这是如何运行的。在for of循环中,yield 1yield 2如我们预期那样直接输出值,但是当遇到yield *语句时,你会发现我们yield另外一个generator函数,并且for..of循环中直接调用foo的迭代器,然后运行完毕后由取回bar的迭代器。

在上面例子中,我们使用了for of循环,实际上,即使是手动调用,结果也和上面一样。

function *foo() {
    var z = yield 3;
    var w = yield 4;
    console.log( "z: " + z + ", w: " + w );
}

function *bar() {
    var x = yield 1;
    var y = yield 2;
    yield *foo(); // `yield*` delegates iteration control to `foo()`
    var v = yield 5;
    console.log( "x: " + x + ", y: " + y + ", v: " + v );
}

var it = bar();

it.next();      // { value:1, done:false }
it.next( "X" ); // { value:2, done:false }
it.next( "Y" ); // { value:3, done:false }
it.next( "Z" ); // { value:4, done:false }
it.next( "W" ); // { value:5, done:false }
// z: Z, w: W

it.next( "V" ); // { value:undefined, done:true }
// x: X, y: Y, v: V

虽然我们展示往下一级的委托,但是foo也可以继续往下委托给别的generator。

另外一个比较诡异的地方就是yield *可以从被委托的generator函数获取return的值(普通的yield语句的值为通过next方法传入的)

function *foo() {
    yield 2;
    yield 3;
    return "foo"; // return value back to `yield*` expression
}

function *bar() {
    yield 1;
    var v = yield *foo();
    console.log( "v: " + v );
    yield 4;
}

var it = bar();

it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }
it.next(); // { value:3, done:false }
it.next(); // "v: foo"   { value:4, done:false }
it.next(); // { value:undefined, done:true }

就像你所看到的,yield *foo()将交出了迭代控制权直到交出去的迭代完成了,迭代完成时,yield *语句的将返回被委托的generator函数的返回值(此例中为foo函数,返回值为foo字符串)

这是yieldyield *的显著区别:yield语句的返回值是通过next()方法传入的,而yield *语句的返回值是被委托generator函数的返回值。

你也可以通过yield *语句从里外两个方向处理错误,

function *foo() {
    try {
        yield 2;
    }
    catch (err) {
        console.log( "foo caught: " + err );
    }

    yield; // pause

    // now, throw another error
    throw "Oops!";
}

function *bar() {
    yield 1;
    try {
        yield *foo();
    }
    catch (err) {
        console.log( "bar caught: " + err );
    }
}

var it = bar();

it.next(); // { value:1, done:false }
it.next(); // { value:2, done:false }

it.throw( "Uh oh!" ); // will be caught inside `foo()`(译者注:当调用了throw方法,err被catch后,发生err的语句之后的代码都被执行,直到下一个yield停下。)
// foo caught: Uh oh!

it.next(); // { value:undefined, done:true }  --> No error here!
// bar caught: Oops!

如上面代码所示,throw('Uh oh!')语句在foo函数内部抛出了一个错误并且被foo函数内部的try...catch所捕获。类似的,在foo函数内部的throw 'Oops!'所抛出的错误也被外部的try...catch所捕获。如果错误内外都没有被捕捉,则会被再外层函数所捕捉。

总结

Generator函数在内部代码有着同步执行的特性,这表明你可以使用try...catch来捕捉其中的错误。generator的迭代器的throw方法可以将错误抛入generator上一次所停留的位置,所以也理所当然的可以被try...catch所捕获。

yield*语句你将迭代权交给另一个generator,yield *语句的返回值就像个传递器,可以传递信息和错误。

但是,目前还有一个问题没有回答,generator函数式如何帮助我们实现异步的,我们目前所了解的都是同步的调用。关键在于创建一个控制generator暂停和执行的一个机制。我们在下一篇文章会进行讲解。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容