David Walsh.name/ES6-Generat…
If you’re not familiar with ES6 generators, read and try the code in “1. ES6 Generator Basics.” After you feel like you’ve got the basics down, we can move on.
Exception handling
One of the most powerful parts of the ES6 generator design is that the code inside the generator is semantically synchronous, although the external iteration control is asynchronous.
A simple or complex way of saying this is that you can use a very familiar error-handling technique — namely try.. The mechanism of the catch.
Such as:
function *foo() { try { var x = yield 3; console.log( "x: " + x ); // May never execute here! } catch (err) { console.log( "Error: " + err ); }}Copy the code
Although the function is paused at the expression yield 3 and remains paused for any length of time, whenever an error is passed back to the generator, try.. Catch can still catch! This is like having the asynchronous capability of traditional callback functions. 🙂
But how does an error get passed back to the generator?
var it = foo(); var res = it.next(); // {value:3, done:false} // Here is no longer another 'next(..). 'call to continue execution, // instead produces an exception: it. Throw ("Oops!" ); // Error: Oops!Copy the code
Obviously, reverse exception handling is also possible:
function *foo() { var x = yield 3; var y = x.toUpperCase(); // Raises a TypeError! yield y; } var it = foo(); it.next(); // { value:3, done:false } try { it.next( 42 ); // '42' has no 'toUpperCase()' method} catch (err) {console.log(err); // TypeError (from calling 'toUpperCase()')}Copy the code
Delegate generator
Another thing you might want to do is call another generator inside a generator function. I’m not talking about normal invocation, but delegating iteration control to another generator. To do this, we use a variation of the yield keyword: yield * (” yield asterisk “).
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 5Copy the code
For the same reason you used function* foo() {} instead of function* foo() {} in part 1, use yield* foo() instead of yield* foo() in other articles or documents. I think it would be more accurate and clear about the purpose.
Now let’s break down the process. Yield 1 and yield 2 return the value directly to for.. The (implied) next() call to the of loop, which we already know.
Yield * then showed up, in this case actually jumping (yielding) to the past by instantiating foo() so yield 3 and yield 4 would return values for.. Of circulation.
Once yield* completes, control reverts to the original generator, and yield 5 is invoked.
For simplicity, I’m just returning all the values. However, you can of course use next(..) To manually invoke and pass in data, the data will also be passed in via the yield* delegate as expected:
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: VCopy the code
Only one level of delegation is demonstrated here, but there’s no reason why *foo() can’t delegate to another generator, and more.
A “trick” is that yield can get a return value from the delegate 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 }Copy the code
As you can see, yield *foo() delegates iteration control (the next next() call) until the generator of the delegate completes, at which point the return value of foo() (in this case the string value “foo”) is returned as the yield * expression value and then assigned to the local variable v.
This is an interesting difference between yield and yield* : using the yield expression, the result is followed by next(..). Returns, but using the yield* expression, returns the value of the delegate generator (because next(..)). The returned data is passed to the delegate generator).
Exception handling can be performed on the yield* delegate in two directions (see above) :
function *foo() { try { yield 2; } catch (err) { console.log( "foo caught: " + err ); } yield; // Pause // throw the exception 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!" ); // Foo caught: Uh oh! it.next(); // {value:undefined, done:true} --> // bar caught: Oops!Copy the code
In the above example, throw(“Uh oh!” ) passes the exception to the try inside *f00() via the yield* delegate. The catch. Similarly, the throw “Oop!” inside *foo() Passes the exception to *bar(), which passes through another try.. Catch caught the exception. If these two exceptions are not caught, they are passed up as expected.
conclusion
Generators have synchronous execution semantics, which means that a try.. Catch to handle yield statement errors. The generator iterator also has a throw(..) Method is used to throw an exception to the generator at the pause position, which can also be done via the generator’s internal try.. Catch, catch, catch
Yield * makes it possible to delegate iteration control from the current generator to another generator. The yield* result can be crossed in both directions, meaning that both data and errors can be passed.
So far, however, one basic question remains unanswered: How can generators help us with asynchronous code patterns? What we have seen in both articles are synchronous iterations of generator functions.
The key is to construct a mechanism that allows the generator to pause to start an asynchronous task and wait for the asynchronous task to finish before continuing (via the iterator’s next() call). In the next article, we’ll explore several ways to create asynchronous control through generators. Stay tuned!
as
The case in this article for generator delegates is one where yield* expressions simply accept values that support the Iterable interface, such as arrays, strings. Therefore, it is also supported:
function* foo() {
yield* [1, 2, 3, 4, 5];
}Copy the code
Iterating over the generator object from the generator function above further yields all the values of the array.
Reference: yield* -mDN