Talk about the issue of generator objects and their relation to function calls?
domenic opened this issue · comments
This might not be in-scope, or at least not fit in to the flow of the narrative. But one thing that always struck me as interesting was whether calling a function should execute its body once or more than once.
For promises and async functions, the function's body is executed upon calling the function; calling .then
on the return value observes it.
For (sync) generators and generator functions in JS, the function's body kind of "starts" upon calling the function, but immediately suspends until .next
is called on the return value. This is because the function returns a generator, which is an iterable-iterator (i.e. an iterator whose [Symbol.iterator]()
method returns this
)
Notably, if you do
var g = genFun();
for (var i of g) { ... }
for (var j of g) { ... }
the body of genFun
will only be called once, and in fact the second loop will immediately exit, doing nothing.
For (sync) "generator functions" in C# (which don't have yield-back-into capabilities), the function's body executes once for each time you iterate through it. This is because they return enumerables, not enumerators. So when you do
var enumerable = genFun();
foreach (var i in enumerable) { ... }
foreach (var j in enumerable) { ... }
the body of genFun
will be called twice, with both loops going through the resulting sequences, because each loop gets its own enumerator from enumerable
.
Jafar's proposal for async generators as observables resembles C# in this regard, with
var observable = asyncGenFun();
for (var i on observable) { ... }
for (var j on observable) { ... }
calling the body of asyncGenFun
twice. Each for-on
desugars to a call to observable.observe({ next, throw, return })
, where each concurrently-executing body of asyncGenFun
calls into their respective { next, throw, return }
tuples.
I'd love a discussion about what tradeoffs are involved here, perhaps related to iterables vs. iterators.