The Basics Of ES6 Generators
ES6生成器的基础
Diving Deeper With ES6 Generators
使用ES6生成器更深入地学习
Going Async With ES6 Generators
与ES6生成器异步
Getting Concurrent With ES6 Generators
与ES6生成器并发
If you're still unfamiliar with ES6 generators, first go read and play around with the code in "Part 1: The Basics Of ES6 Generators". Once you think you've got the basics down, now we can dive into some of the deeper details.
如果您仍然不熟悉ES6生成器,请首先阅读并使用“第1部分:ES6生成器的基础知识”中的代码。 一旦您认为自己掌握了基础知识,现在我们就可以深入研究一些更深层次的细节。
One of the most powerful parts of the ES6 generators design is that the semantics of the code inside a generator are synchronous, even if the external iteration control proceeds asynchronously.
ES6生成器设计最强大的部分之一是,即使外部迭代控制是异步进行的,生成器内部代码的语义也是同步的 。
That's a fancy/complicated way of saying that you can use simple error handling techniques that you're probably very familiar with -- namely the try..catch mechanism.
这是一种幻想/复杂的说法,即可以使用您可能非常熟悉的简单错误处理技术,即try..catch机制。
For example:
例如:
function *foo() { try { var x = yield 3; console.log( "x: " + x ); // may never get here! } catch (err) { console.log( "Error: " + err ); } }Even though the function will pause at the yield 3 expression, and may remain paused an arbitrary amount of time, if an error gets sent back to the generator, that try..catch will catch it! Try doing that with normal async capabilities like callbacks. :)
即使函数将在yield 3表达式处暂停,并且可能在任何时间都保持暂停状态,但是如果将错误发送回生成器,则try..catch将捕获该错误! 尝试使用普通的异步功能(例如回调)来实现。 :)
But, how exactly would an error get sent back into this 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!Here, you can see we use another method on the iterator -- throw(..) -- which "throws" an error into the generator as if it had occurred at the exact point where the generator is currently yield-paused. The try..catch catches that error just like you'd expect!
在这里,你可以看到我们采用了迭代器的另一种方法- throw(..) -这“抛出”的错误到发电机,就好像它在精确的点,其中发电机目前正在发生的yield -paused。 try..catch会像您期望的那样捕获该错误!
Note: If you throw(..) an error into a generator, but no try..catch catches it, the error will (just like normal) propagate right back out (and if not caught eventually end up as an unhandled rejection). So:
注意:如果将错误throw(..)到生成器中,但是没有try..catch捕获到该错误,则该错误将(像正常情况一样)立即传播回(如果未被捕获,最终将导致未处理的拒绝)。 所以:
function *foo() { } var it = foo(); try { it.throw( "Oops!" ); } catch (err) { console.log( "Error: " + err ); // Error: Oops! }Obviously, the reverse direction of error handling also works:
显然,错误处理的相反方向也可以:
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) }Another thing you may find yourself wanting to do is call another generator from inside of your generator function. I don't just mean instantiating a generator in the normal way, but actually delegating your own iteration control to that other generator. To do so, we use a variation of the yield keyword: yield * ("yield star").
您可能会发现自己想做的另一件事是从生成器函数内部调用另一个生成器。 我不仅意味着以常规方式实例化生成器,而且实际上是将自己的迭代控制委托 给其他生成器。 为此,我们使用yield关键字的变体: yield * (“ yield star”)。
Example:
例:
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 5Just as explained in part 1 (where I used function *foo() { } instead of function* foo() { }), I also use yield *foo() here instead of yield* foo() as many other articles/docs do. I think this is more accurate/clear to illustrate what's going on.
就像在第1部分中解释的那样(在这里我使用function *foo() { }代替function* foo() { } ),我在这里也使用yield *foo()代替了yield* foo()就像其他许多文章/文档一样做。 我认为这更准确/清楚地说明了正在发生的事情。
Let's break down how this works. The yield 1 and yield 2 send their values directly out to the for..of loop's (hidden) calls of next(), as we already understand and expect.
让我们分解一下它是如何工作的。 如我们已经理解和期望的那样, yield 1和yield 2将它们的值直接发送到for..of循环的(隐藏) next()调用。
But then yield* is encountered, and you'll notice that we're yielding to another generator by actually instantiating it (foo()). So we're basically yielding/delegating to another generator's iterator -- probably the most accurate way to think about it.
但是然后遇到yield* ,您会注意到我们通过实际实例化它来生成另一个生成器( foo() )。 因此,我们基本上是在屈服/委托给另一个生成器的迭代器-可能是考虑它的最准确方法。
Once yield* has delegated (temporarily) from *bar() to *foo(), now the for..of loop's next() calls are actually controlling foo(), thus the yield 3 and yield 4 send their values all the way back out to the for..of loop.
一旦yield* (临时)从*bar()委托给*foo() ,现在for..of循环的next()调用实际上控制着foo() ,因此yield 3和yield 4发送它们的值返回到for..of循环。
Once *foo() is finished, control returns back to the original generator, which finally calls the yield 5.
*foo()完成后,控制权返回到原始生成器,该生成器最终将yield 5称为yield 5 。
For simplicity, this example only yields values out. But of course, if you don't use a for..of loop, but just manually call the iterator's next(..) and pass in messages, those messages will pass through the yield* delegation in the same expected manner:
为简单起见,此示例仅yield s值。 但是当然,如果您不使用for..of循环,而只是手动调用迭代器的next(..)并传递消息,则这些消息将以相同的预期方式通过yield*委派:
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: VThough we only showed one level of delegation here, there's no reason why *foo() couldn't yield* delegate to another generator iterator, and that to another, and so on.
尽管我们在这里只显示了一个委托级别,但没有理由为什么*foo()无法将yield*委托给另一个生成器迭代器,也不能yield*委托给另一个生成器迭代器,等等。
Another "trick" that yield* can do is receive a returned value from the delegated generator.
yield*可以执行的另一个“技巧”是从委托的生成器接收return ed值。
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 }As you can see, yield *foo() was delegating iteration control (the next() calls) until it completed, then once it did, any return value from foo() (in this case, the string value "foo") is set as the result value of the yield* expression, to then be assigned to the local variable v.
如您所见, yield *foo()委派了迭代控制( next()调用),直到完成为止,然后一旦完成,则foo()任何return值(在这种情况下,字符串值"foo" )为设置为yield*表达式的结果值,然后将其分配给局部变量v 。
That's an interesting distinction between yield and yield*: with yield expressions, the result is whatever is sent in with the subsequent next(..), but with the yield* expression, it receives its result only from the delegated generator's return value (since next(..) sent values pass through the delegation transparently).
这是yield和yield*之间的有趣区别:使用yield表达式,结果是随后的next(..)发送的结果,但是使用yield*表达式,它仅从委托生成器的return值接收结果(因为next(..)发送的值透明地通过委派。
You can also do error handling (see above) in both directions across a yield* delegation:
您还可以在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!As you can see, the throw("Uh oh!") throws the error through the yield* delegation to the try..catch inside of *foo(). Likewise, the throw "Oops!" inside of *foo() throws back out to *bar(), which then catches that error with another try..catch. Had we not caught either of them, the errors would have continued to propagate out as you'd normally expect.
如您所见, throw("Uh oh!")通过yield*委托将错误抛出到*foo()内部的try..catch中。 同样, throw "Oops!" *foo()内部将返回*bar() ,然后通过另一个try..catch捕获该错误。 如果我们没有抓住它们中的任何一个,那么错误将继续像您通常期望的那样传播出去。
Generators have synchronous execution semantics, which means you can use the try..catch error handling mechanism across a yield statement. The generator iterator also has a throw(..) method to throw an error into the generator at its paused position, which can of course also be caught by a try..catch inside the generator.
生成器具有同步执行语义,这意味着您可以在yield语句中使用try..catch错误处理机制。 生成器迭代器还具有throw(..)方法,可在生成器的暂停位置向生成器中抛出错误,当然,生成器内部的try..catch也可以捕获该错误。
yield* allows you to delegate the iteration control from the current generator to another one. The result is that yield* acts as a pass-through in both directions, both for messages as well as errors.
yield*允许您将迭代控制从当前生成器委派给另一个生成器。 结果是yield*充当消息和错误的双向传递。
But, one fundamental question remains unanswered so far: how do generators help us with async code patterns? Everything we've seen so far in these two articles is synchronous iteration of generator functions.
但是,到目前为止,仍然没有一个基本问题可以回答:生成器如何通过异步代码模式帮助我们? 到目前为止,我们在这两篇文章中看到的都是生成器函数的同步迭代。
The key will be to construct a mechanism where the generator pauses to start an async task, and then resumes (via its iterator's next() call) at the end of the async task. We will explore various ways of going about creating such asynchronicity-control with generators in the next article. Stay tuned!
关键是要构建一种机制,其中生成器暂停以启动异步任务,然后在异步任务结束时继续(通过其迭代器的next()调用)。 在下一篇文章中,我们将探讨使用生成器创建异步控制的各种方法。 敬请关注!
翻译自: https://davidwalsh.name/es6-generators-dive
相关资源:jdk-8u281-windows-x64.exe