#深入ES6Generators(译)
原文地址:http://davidwalsh.name/es6-generators-dive
ES6 Generators:全系列
- The Basics Of ES6 Generators
- Diving Deeper With ES6 Generators
- Going Async With ES6 Generators
- Getting Concurrent With ES6 Generators
如果你仍然不熟悉ES6 generator,首先去读一读并理解“第一部分:ES6Generator基础”。一旦你认为你已经了解了这些基础,现在我们就可以深入到一些细节之中了。
错误处理
ES6 generator设计时一个最为强大的部分就是它内部的代码语义是同步的,即使外部迭代控制是异步执行的。
这是个很棒而且复杂的方式,意味着你可以使用简单的错误处理技术,这个技术你可能已经非常熟悉了 – 也就是try..catch
机制
例如:
1 | try { |
尽管函数会在yield 3
表达式处停止,并且可能随意暂停一段时间,如果一个错误没回传到generator,那个try..catch
会捕获它!尝试用普通的异步能力(例如callback)来做做看?:)
但是,怎样才能使一个错误返回到generator中呢?
1 | var it = foo(); |
这里,你可以看到我们使用了iterator上的另一个方法 – throw(..)
– 它会“抛”一个异常到generator中,就像它正好在generator当前yield
暂停处发生一样。try..catch
捕获如你所期望的那样这个错误!
注:如果你throw(..)
一个错误进入generator,而没有try..catch
住它,那么这个错误会(和普通错误一样)传播会来(如果最终没有被捕获,则会成为一个未被捕获的异常)。因此:
1 | function *foo() { } |
显然,反方向的错误处理方式同样可行:
1 | function *foo() { |
generator代理
你可能想做的另一件事情是从你的generator函数内部调用另一个generator。我指的并不仅仅是普通方式实例化一个generator,而是代理你自己的的迭代控制到那一个另外的generator上。
例:
1 | function *foo() { |
如在第一部分解释的那样(我使用function *foo(){ }
而不是function* foo(){ }
),我这里同样使用yield *foo()
来代替很多其他文章/文档使用的yield* foo()
。我认为这样可以更加准确/清晰地说明代码正在做些什么。
让我们分解一下看看它是如何工作的。yield 1
和yield 2
直接发送他们的值到for..of
循环外的next()
的(隐式)调用中,就像我们已经理解/期望的那样。
但是接着程序就走到了yield*
,然后你会注意到,我们正在通过实例化它(foo())yield到另一个generator。因此我们基本上是在yield或代理到另一个generator的iterator上 – 这可能是最准确的想象它的方式。
一旦yield*
被(临时)从*bar()
代理到*foo()
,那么现在for..of
循环的next()
调用实际上是在控制foo()
,因此yield 3
和yield 4
发送他们的值到外面的for..of
循环中。
一旦*foo()
结束,控制权会返回到最初的generator上,它最终调用到yield 5
为了简化起见,这个例子仅仅yield
值出去,但是当然如果你不适用一个for..of
循环,而是就手动调用iterator的next(..)
并且通过消息传递,这些消息会通过同样的行为穿过yield*
代理:
1 | function *foo() { |
尽管我们在这里只显示了一层代理,而*foo()
去yield*
代理到另一个、另一个、另一个generator也是可以的。
1 | function *foo() { |
如你所见,yield *foo()
代理迭代循环(这些next()
调用)知道结束,然后一旦它这样做,任何从foo()
中return
的值(在本例里:字符串"foo"
)会被设置为yield*
表达式的结果值,然后被赋值给本地变量v
。
yield
和yield*
之间有一个有趣的区别:对于yiled
表达式,其结果是任何同事子序列next(..)
传入的值,但是对于yield*
表达式,它仅仅接收被代理的generator的return
值(因为next(..)
发送的值是对代理透明的)。
你也可以通过yield*
代理从两个方向进行错误处理(如前所示):
1 | function *foo() { |
如你所见,throw("Uh oh!")
通过yield*
代理抛出错误到*foo()
内部的try..catch
中。同样的,*foo()
内部的throw "Oops!"
抛回到*bar()
中,然后它在另一个try..catch
中捕获这个错误,如果我们没有捕获其中的某一个,那么这些错误会继续向外传播像你平常期望的那样。
总结
Generator具有同步的语法,这表示你可以穿过yield
表达式使用try..catch
错误处理机制。generator同时也有一个throw(..)
方法将一个错误抛入generator的暂停位置,这当然也可以被generator内部的try..catch
捕获。
yield*
允许你从当前的generator代理迭代控制到另一个generator。其结果是yield*
表现得像一个连接两个方向的通道,同时可供传递消息以及错误。
但是,到现在位置,我们还有一个基本的问题没有解决:generator是如何帮助我们处理异步模式的呢?我们现在在这两篇文章中所见的一切都是generator函数的同步迭代。
其中的关键就是建立一种机制,generator暂停以开始一个异步任务,然后在异步任务结束时恢复(通过他的iterator的next()
调用)。我们将在下一篇文章中探索各种关于使用generator创造这样的异步控制机制的方式。请继续关注!