What is the difference between these code fragments?

yield *smth; 
 for (let x of smth) { yield x; } 

@torazaburo in the comment said

This is an apt result of the generator of the generator. It is not correct to throw it into the generator, etc.

I would like to know what exactly the difference is and in what cases it will manifest itself.

  • I never used yield, I won’t even think where it could be shoved =) - Serge Esmanovich
  • @SergeEsmanovich, Generators - Grundy
  • @Grundy I’m aware of what this is and I understand about how it works, but I have never used it yet, I know that instead of a callback, I can use it to suspend the execution of the function, but as is old - fashioned - Serge Esmanovich
  • @SergeEsmanovich, alternate passage through the matrix with the ability to return each cell for individual processing - once I saved a lot of nerves. - user207618
  • @Other Thank you, look this way - Serge Esmanovich

2 answers 2

In fact, the syntax yield * gen used to transfer control to another generator.

This is what MDN says about it:

The result of this expression is iterates over the operand.

Here is an example of how this works:

 let seqPos = function * (max) { for (let i = 1; i <= max; i++) { yield i; } } let seqNeg = function * (min) { let start = min > 0 ? -1*min : min; for (let i = start; i < 0; i++) { yield i; } } let seq = function * (val) { yield * seqNeg(-1*val); yield 0; yield * seqPos(val); } for (let i of seq(2)) { console.log(i); } 

The example above will output:

-2
-one
0
one
2

And here is the JSFiddle with an example.


As for the main issue, the difference between the structures:

 for (let val of gen) { yield val; } 

and

 yield * gen; 

is that in the first case, only the values ​​of the child generator are returned, and in the second, control is completely transferred to the child generator.

This means that the call to the Generator.prototype.next , Generator.prototype.throw and Generator.prototype.return methods by the client code on the parent generator will be addressed to the child generator:

 let innerGen = function * () { try { for (let i = 0; i < 3; i++) { console.log(yield i); } } catch (e) { console.log('Got you!'); } } let outerGen = function * () { let child = innerGen(); yield * child; } let g = outerGen(); g.next(); g.next("foo"); // выведет в консоль "foo" g.throw(new Error('Oops!')); // выведет "Got you!" 

The situation with forwarding exceptions to the child iterator described by the participant @Roman Paradeev in the next answer is just a special case of the transfer of control to the child generator.

  • @Qwertiy Look, what a wonderful answer. Mark it, please, and I'll delete my. - Roman Paradeev
  • @RomanParadeev, ok, I will note. But do not delete. Also, notifications don't work that way. - Qwertiy

This is what the ECMAScript Wiki says about this:

This is a loop through the generator, except that it can be passed through the generator.

So the difference seems to be only in forwarding exceptions.

I have sketched out a small example that illustrates the difference:

 function * delegate() { try { yield; } catch (error) { console.log('Caught by delegate'); } } function * gen1() { console.log('Generator 1'); try { yield * delegate(); } catch (error) { console.log('Caught by generator'); } } function * gen2() { console.log('Generator 2'); try { for (let x of delegate()) { yield x; } } catch (error) { console.log('Caught by generator'); } } [gen1, gen2].forEach(gen => { const genObj = gen(); genObj.next(); genObj.throw('Uh oh...'); }); 

As expected, the output will be next

 Generator 1 Caught by delegate Generator 2 Caught by generator 
  • Does the calling code throw exceptions inside the callee? Suddenly. Why is this even necessary? - Qwertiy
  • "similar to a for-in loop" for-in or for-of? - Qwertiy
  • You can transfer data to the generator, so why not pass exceptions? - Roman Paradeev
  • With for-in , most likely an error in the wiki. - Roman Paradeev