Ah, I'm understanding things better now.
In your second approach, what would this do?
let tmp1 = 1;
let tmp2 = 2;
async {
tmp1 = async f();
tmp2 = async g();
console.log(tmp1 + tmp2)
}
Does this log 3, because it used the old value of tmp1 and tmp2? Or is it a reference error? Does assigning to a variable put the variable in an undefined state until the promise has settled?
I'm going to focus on your first approach, as that currently makes more sense to me, due to the issue above. In this approach, if we ignore the case where you put async
in the last expression (I'll come back to that later), then there's really no difference between this in the parallel const
idea, except for potentially nicer syntax, and the fact that there's better support for fire-and-forget.
For example:
const res = async {
const x = async f()
const someParam = calcParam()
const y = async g(someParam)
async fireAndForget()
x + y
}
// is the same as
const someParam = calcParam()
parallel const
x = async f(),
y = async g(someParam),
_ = fireAndForget()
const res = x + y
Even though the async {}
syntax allows for any arbitrary synchronous expression to be intermixed between the async ones, there's actually no need to do so. Those synchronous expressions can't ever depend on the results of these async operations, so they can always be placed beforehand instead of being intermixed. The async
construct itself can only ever be used to assign to something, or in a fire-and-forget scenario - it's invalid to try and use it in any other way, as that would require using the resolved value, which would be a ReferenceError within the block (if we're not at the last expression).
I'm mostly pointing this out, because this async {}
syntax has the appearance of being able to have much more flexibility than paralell const
, but in reality, I think they're almost the same, except for the extra power it has in the last expression. This isn't a bad thing - it just took me some time to figure out that there wasn't anything extra going on with this except for a different syntax.
Now, about async
being used in the last expression (e.g. async { async f() + async g() }
) - from what I can understand, this will have pretty much the same semantics as the await.multi idea. You're going to run into the same issues that async.multi had, where we need to define a deterministic execution order for this expression. I'll just assume that it uses the same algorithm as await.multi (where it executes everything after async
first, then the rest of the expression), unless you have a different idea you want to share.
Alright, with all of that in mind, I'll try to build yet another proposal using these previous ideas :p. I see that my previous await.multi
syntax is not as powerful as the async {}
block idea, but I also feel like there's some clumbsy parts to async {}
that I want to take a stab at solving.
I'm proposing a do interpolated
block, which acts like a do block, but you're allowed to interpolate expressions into it that run before the do block runs.
Example:
const result = do interpolated {
await ${fireAndForget()}
const value = await ${f()}
value + await ${g()} + await ${h()}
}
// is the same as
const result = do {
const $temp1 = fireAndForget()
const $temp2 = f()
const $temp3 = g()
const $temp4 = h()
await $temp1
const value = await $temp2
value + await $temp3 + await $temp4
}
This will let all of the async operations run in advance, before the actual do block executes and we start running things one-at-a-time. I think the interpolation syntax helps make it clear that the particular expression can't depend on anything created within the do block
, i.e. this would be an error:
do interpolated {
const x = f()
2 + ${g(x)} // Error! x is not defined.
}
Thoughts?