Generator return() and exceptions

Allen Wirfs-Brock allen at
Wed Jul 23 12:16:01 PDT 2014

On Jul 23, 2014, at 11:12 AM, Brendan Eich wrote:

> Allen Wirfs-Brock wrote:
>> Now that we have return() it isn't clear to me that we actually need throw() or whether for-of/yield* should call throw() like they currently do.
> Wait, throw is useful anyway, for exception-throwing apart from returning. Right?
> Also I do not know what you mean by "like they currently do".
> I hope this is all pre-caffeine confusion on my part!
> /be

Here's what I'm trying to say:

for (let each of aGenerator) {
     return 42;

I think we all agree, that the loop implicitly show do a:
   aGenerator.return(42);  //or perhaps just: aGenerator.return() ??

but what about:

for (let each of aGenerator) {
     throw new Error;

Do we do:
  aGenerator.throw(theErrorExceptionObject);  //propagate all unhanded exceptions that terminated the loop to the generator
  aGenerator.return(); //unwind the generator because the loop is abnormally terminating, then propagate the exception

The current spec. draft does the first of these, but I'm not totally convinced that is the better alternative.

To me, it seems to come down to how yo think about the generator in relationship to its clients.

If you think about it from the perspective of the generator, then one model is that a |yield| is really just a callback into the client loop and the generator would expect its exception handlers to catch unhanded exceptions generated within the callback. 

If you think about it from the perspective of the loop, the generator is just an (iterator) object on which you are performing 'next' method calls.  There is no particular reason that you would expect an exception occurring in the loop body (or from the loop mechanism) to propagate to the iterator object (or any other known object) as it isn't in the current call chain.  But if the loop abnormally terminates by an unhandled exception you still want to "close" the iterator by invoking its 'return' method.

yield* is similar to a loop, in that (as currently specified) it propagates a throw() delivered to the outer generator to the inner iterator. Does this really make sense?  What relevance is that exception to the inner iterator?  Why shouldn't yield * just call return() on the inner iterator to "close" it?

So going back to my original questions.  Given that we now have return() available to close an abnormally discarded generator, when would somebody (presumably implementing a control abstraction) actually want to apply throw() rather than return() to an iterator?

catch and finally are really two quite different things.  Catch is a dynamically scoped mechanism for finding exception handlers.  Finally, is a unwind mechanism for cleaning up local invariants when a function activation is abnormally terminated. It's clear, to me, why a control abstraction would want to unwind a generator (ie return()) it is discarding.  It's less clear (at least seemingly a lot less common) for a control abstraction to want to splice into the exception handling of a generator).

I guess what I need to see are some motivating user cases for generator throw() where throw is really what is needed rather than return();

Finally, when thinking about these issue if find that 'close' does seem to be a more appropriate name than 'return'.  The mechanism is based upon using a 'return' completion value, but the callers intent seems to be 'close the iterator'  (or unwind the iterator).   

Also, I find some of these questions seem simpler to reason about I think in terms of an iterator with 'next', 'throw' and 'return' methods rather than a generator and its various internal states.  


More information about the es-discuss mailing list