Iterators, generators, finally, and scarce resources
wingo at igalia.com
Wed Apr 30 00:09:38 PDT 2014
On Tue 29 Apr 2014 20:35, David Herman <dherman at mozilla.com> writes:
> it's a bad smell if code that creates an iterator in a for-of loop
> head, loops over it, and *never even touches the iterator directly*
> doesn't shut down the iterator.
There are no other objects in JS that have a finalization facility or
other "shut down" semantics. And yet many of them can indeed hold onto
scarce resources (to the extent that is possible in JS, which is to say,
approximately never in the browser), and none of them have similar
This truly seems a case of the tail wagging the dog.
> - Iterators are intended to be short-lived (in particular, long-lived
> iterators over mutable data sources are invalidation hazards). So the
> common consumer of iterators, for-of, should properly dispose of them.
This is only one of the consumers of iterators. Should exceptions
thrown while destructuring iterators also imply "shutdown"? I also note
this is the first JS construct that would introduce the notion of
resource acquisition and also of implicit finallies.
> - The uncommon case of using an iterator partially and in several
> loops can be easily implemented with combinators (or while loops), but
> we should be safe by default.
Again, it's only the holders of scarce resources that even need to
consider this question of "safety", as it is only in their case that the
question arises! Normal code -- the common case -- cannot be "unsafe".
> - The problems of "on cleanup" logic will be greatly exacerbated with
> future asynchronous iteration features, which are IMO an extremely high
> priority for the future. The overwhelming majority of JS programs that
> operate on sequences of data are doing so asynchronously. The moment we
> start going down the road of designing asynchronous iteration features
> (such as `for (await ... of ...)`), which in fact Jafar and I have been
> starting work on, the try/finally hazards will show up much more
> often. If we don't do proper disposal of synchronous iterators, we'll
> create an asymmetry between synchronous and asynchronous iteration,
> which would not only be a nasty wart but also a refactoring hazard.
Can you elaborate? This is getting quite far afield of the existing
>> Incidentally I think that if TC39 decided to re-add this method, it
>> should be called close() instead, because it doesn't make sense to
>> "return" from a non-generator iterator.
> I thought so at first too, until I remembered that iterators have a
> return value. So I still think return is the right name.
next() and throw() also have return values, FWIW...
>> However in this case it is possible to arrange to close the iterator,
>> with a different interface:
> This is a *dramatic* weakening of the power of iterators, in that you
> force *all* iteration abstractions to expose their external resources to
> consumers. Again, it may not seem like a big deal now but it'll be
> completely unacceptable for the asynchronous case.
Again this argument needs to provide more details on the asynchronous
case, and as regards the solution I propose, I think it makes sense --
it's the same code that has the responsibility for cleaning up either
way, whether it's implicit, opaque, and not configurable (Jafar's
proposal) or explicit and configurable (status quo).
>> == return() in generators is semantically weird
> It's not as weird as you make it out to be. Return is not much
> different from throw, first of all. Note also that ES7 do-expressions
> allow expressions to return.
ES7 do-expressions mark their returns with "return" or otherwise via
tail position AFAIU. My argument was that making "yield" have three
continuations tips the balance for me into bogosity.
Anyway this is going back and forth on nits; I'll just pick a couple
more below. I've probably repeated myself enough over the years!
>> == Calling return() on early exit from for-of is expensive
>> Wrapping a try/finally around each for-of is going to be really
>> expensive in all engines right now. I'm skeptical about our ability to
>> optimize this one away. Avoiding try/catch around for-of was one reason
>> to move away from StopIteration, and it would be a pity to re-impose
>> this cost on every for-of because of what is, in the end, an uncommon
>> use case.
> This glosses over a critical difference: the StopIteration semantics
> required catching exceptions on every iteration of the loop. This
> semantics only requires a check on loop exit.
This is true. And as Filip notes, this isn't a fundamental perf problem
-- it can be worked around. But it's also true (AFAIK) that no engine
optimizes try/finally right now.
>> I think the expected result of doing this would be
>> performance lore to recommend using other iteration syntaxen instead of
> This argument seems fishy to me. There is no comparable syntax in JS for
> for-of and generators, so I think the alternative would be stuff like
> higher-order methods (a la .forEach). I find it hard to believe that a
> single check on the outside of the loop will make or break their ability
> to compete with higher-order methods.
It's a question of whether the loop as a whole is ion'd, dfg'd, ftl'd,
crankshaft'd, etc or not.
More information about the es-discuss