Nested awaits ought to work or barf
As reported by @superjoe30:
atest "nested await", (cb) ->
a = 10
b = 11
await
foo(a, defer x)
await foo(b, defer y)
foo(y, defer z)
cb(x == a and y == b == z, {})
To solve this in coffee, we need to scope __iced_deferrals and have the closing __iced_deferrals.fulfill() be pushed down through the continuation chain.
2 arguments for supporting nested awaits:
- Every other block in coffee-script is nestable. It's confusing to make an exception without a really good reason.
- After switching to iced, I gleefully went to refactor the first function that would really clean up with it, and ran right smack into this issue. Here's the real-life example: https://github.com/superjoe30/groovebasin/blob/218ac76bcff9d94a090e8537290602b611f775ac/src/mpd.coffee#L433
I'd be happy to hear that I'm doing it completely wrong and that there's a canonical "iced" way to solve this, and then maybe this could go into the FAQ.
I agree about point 1, and it gives me great pains to go against it. However, I just don't know how to support monstrosities such as this:
await for i in [0..10]
some_func (await some_other_func i, defer _), defer output[i]
Man! If you would give the benefit of just one (anonymous) helper function, I could make that clean and easy to read. However, without it, things are going to explode in difficulty.
I spent some time today trying to change iced to handle your example, and it broke some other regression tests, leading me to some uncertainty as to how directly nested awaits should work in general. The simple rule of await goes to pieces in some cases like the above.
The canonical way to write your test function above, under the status quo, is this:
a = 10
b = 11
await
foo(a, defer x)
((cb) ->
await foo(b, defer y)
await foo(y, defer z)
cb y, z
)(defer y, z)
cb(x == a and y == b == z, {})
That is, the first call to foo and the anonymous function fire in parallel. The anonymous function then has some serial stuff going on. Personally, I'm really happy with this solution. It's not much additional code, and it still obeys the simple rule of await.
The code example that you gave at the beginning doesn't make sense to me:
await for i in [0..10]
some_func (await some_other_func i, defer _), defer output[i]
What would be the expected behavior of this? That looks like a syntax error to me.
The canonical example you gave above makes sense, but I argue that it's not nearly as easy to read as a nested await block would be. And the whole point of iced is to make async code easy to read and modify.
This is killing me right now. :) Instead of:
await
callback = defer error
await doSomething defer(error)
return callback error if error?
await doSomethingElse defer(error)
return callback error if error?
...
console.error error if error?
I have to do ugly things. I'm not used to having ugly code when writing in Iced. :) You could say that in that case one could use async.waterfall or something but I have more arguments in the inner callbacks which need to persist a little bit more and it would not be nice with async.
One more vote for this. I think it would be useful.
Has there been any progress on this issue in the last eight months?
I find the provided workaround terribly difficult to read. ie...
await
foo(a, defer x)
((cb) ->
await foo(b, defer y)
await foo(y, defer z)
cb y, z
)(defer y, z)
cb(x == a and y == b == z, {})
The example 'tough' situation makes sense to me. Max would you mind sharing the code that 'broke some regression tests'? Were you annotating the 'awaits' by working out from the 'defers' as a pre-processing step?
await for i in [0..10]
some_func (await some_other_func i, defer _), defer output[i]