本文翻译自:Diving Deeper With ES6 Generatorsjavascript
因为我的能力有限,翻译中不免有纰漏和错误,望不吝指正issuejava
若是你依然对ES6 generators不是很熟悉,建议你阅读本系列第一篇文章“第一部分:ES6 Generators基础指南”,并练习其中的代码片断。一旦你以为对基础部分掌握透彻了,那咱们就能够开始深刻理解Generator函数的一些细节部分。异步
ES6 generators设计中最为强大部分莫过于从语义上理解generator中的代码都是同步的,尽管外部的迭代控制器是异步执行的。async
也就是说,你可使用简单的错误处理技术来对generators函数进行容错处理, 也就是你最为熟悉的try...catch
机制。ide
例如:函数
function *foo() { try { var x = yield 3; console.log( "x: " + x ); // may never get here! } catch (err) { console.log( "Error: " + err ); } }
尽管上面例子中的foo
generator函数会在yield 3
表达式后暂停执行,而且可能暂停任意长的时间,若是向generator函数内部传入一个错误,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
模块捕获了经过throw
方法抛出的错误。
注意:若是你经过throw(..)
方法向generator函数内部抛出一个错误,同时在函数内部又没有try...catch
模块来捕获错误,该错误(如同正常的错误冒泡机制)将从generator函数冒泡到函数外部(若是始终都没对该错误进行处理,该错误将冒泡到最外层成为未捕获错误)。代码以下:
function *foo() { } var it = foo(); try { it.throw( "Oops!" ); } catch (err) { console.log( "Error: " + err ); // Error: Oops! }
显而易见,反向的错误处理依然可以正常工做(译者注:generator函数内部抛出错误,在generator外部捕获):
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函数的过程当中,另一件你可能想要作的事就是在generator函数内部调用另一个generator函数。这儿我并非指在普通函数内部执行generator函数,其实是把迭代控制权委托给另一个generator函数。为了完成这件工做,咱们使用了yield
关键字的变种:yield *
(“yield star”)。
例如:
function *foo() { yield 3; yield 4; } function *bar() { yield 1; yield 2; yield *foo(); // `yield *` delegates iteration control to `foo()` yield 5; } for (var v of bar()) { console.log( v ); } // 1 2 3 4 5
在第一篇文章中已经说起(在第一篇文章中,我使用function *foo() { }
的语法格式,而不是function* foo() { }
),在这里,咱们依然使用yield *foo()
,而不是yield* foo()
,尽管不少文章/文档喜欢采用后面一种语法格式。我认为前面一种语法格式更加准确/清晰得表达此语法含义。
让咱们来分解上面代码是如何工做的。yield 1
和yield 2
表达式直接将值经过for..of
循环(隐式)调用next()
传递到外部,正如咱们已经理解并期待的那样。
在代码执行过程当中,咱们遇到了yield *
表达式,你将看到咱们经过执行foo()
将控制权交给了另一个generator函数。所以咱们基本上就是出产/委托给了另一个generator函数的迭代器- -也许这就是最准确的理解代理generator函数如何工做的。
一旦yield *
表达式(临时的)在*bar()
函数中将控制权委托给*foo()
函数,那么如今for..of
循环中的next()
方法的执行将彻底控制foo()
,所以yield 3
和yield 4
表达式将他们的值经过for..of
循环返回到外部。
当*foo()
运行结束,控制权从新交回最初的generator函数,最后在外层bar
函数中执行yield 5
。
简单起见,在上面的实例中,咱们仅经过yield
表达式将值传递到generator函数外部,固然,若是咱们不用for..of
循环,而是手动的执行迭代器的next()
方法来向函数内部传递值,这些值也会按你所期待的方式传递给经过yield *
代理的generator函数中:
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
尽管上面的代码中咱们只展现了嵌套一层的代理generator函数,可是没有理由*foo()
不能够经过yield *
表达式继续代理其余的generator迭代器,甚至继续嵌套代理其余generator函数,等等。
yield *
表达式能够实现另一个窍门,就是yield *
表达式将会返回被代理generator函数的函数返回值。
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()
正在代理迭代器的控制权(调用next()
方法)至到其运行完成,当前执行完成,foo()
函数的函数return
值(本例中是"foo"
字符串)将会做为yield *
表达式的值,在上例中将该值赋值给变量v
。
这是一个yield
和yield*
表达式有趣的区别:在yield
表达式中,表达式的返回值是经过随后的next()
方法调用传递进来的,可是在yield *
表达式中,它将获取到被代理generator函数的return
值(由于next()
方法显式的将值传递到被代理的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()` // foo caught: Uh oh! it.next(); // { value:undefined, done:true } --> No error here! // bar caught: Oops!
如你所见,throw("Uh oh!")
经过yield*
代理将错误抛出,而后*foo()
函数内部的try..catch
模块捕获到错误。一样地,在*foo()
函数内部经过throw "Oops!"
抛出错误冒泡到*bar()
函数中被另一个try..catch
模块捕获,若是咱们没有捕获到其中的某一条错误,该错误将会按你所期待的方式继续向上冒泡。
Generators函数拥有同步执行的语义,这也意味着你能够经过try..catch
错误处理机制来横跨yield
语句进行错误处理。同时,generator迭代器有一个throw()
方法来向generator函数中暂停处抛出一个错误,该错误依然能够经过generator函数内部的try..catch
模块进行捕获处理。
yield *
关键字容许你将迭代控制权从当前generator函数委托给其余generator函数。结果就是,yield *
将扮演一个双向的信息和错误传递角色。
可是到目前为止,一个基础的问题依然没有解决:generator函数怎么帮助咱们处理异步模式?在以上两篇文章中咱们一直讨论generator函数的同步迭代模式。
构想generator函数异步机制的关键点在于,经过generator函数的暂停执行来开始一个异步任务,而后经过generator函数的从新启动(经过迭代器的next()
方法的执行)来结束上面的异步任务。咱们能够在接下来的文章中发现generator函数形式各样的异步控制机制。近期期待!