Would it be possible to add “await on first use” to the language?

# Šime Vidas (8 years ago)

Daniel Brain from PayPal has written a post about async/await: medium.com/@bluepnume/even-with-async-await-you-probably-still-need-promises-9b259854c161

It revolves around writing an async function which would execute three tasks in parallel like so:

|--------- dough --------->
|---- sauce ----> |-- cheese -->

The code used as a starting point was:

async function makePizza(sauceType = 'red') {

  let dough  = await makeDough();
  let sauce  = await makeSauce(sauceType);
  let cheese = await grateCheese(sauce.determineCheese());

  dough.add(sauce);
  dough.add(cheese);

  return dough;
}

This pattern, of course, cases the tasks to execute in sequence, like so:

|-------- dough --------> |-------- sauce --------> |-- cheese -->

The remainder of the post introduces several solutions in an attempt to achieve optimal concurrency. For instance, the author’s preferred solution uses a custom memoize function and Promise.all.

Compared to the initial code above, these solutions seem complex, almost as if the language does not have the appropriate syntactic forms and/or APIs to address this particular use case.

This got me thinking. What if there was a version of await that doesn’t pause execution on the spot, but continues execution until the variable which the await is assigned to, is first referenced?

I’ve added comments to mark the positions where execution pauses:

async function makePizza(sauceType = 'red') {

  let dough  = await makeDough();
  let sauce  = await makeSauce(sauceType);
  let cheese = await grateCheese(/* pause to await sauce */sauce.determineCheese());

  /* pause to await dough */ dough.add(sauce);
  dough.add(/* pause to await cheese */ cheese);

  return dough;
}

Please let me know if adding a await-like keyword that works like this would be a bad idea. From a layman’s perspective, it seems that having this feature would simplify code patterns which involve execution in parallel like the example described in the article.

# Domenic Denicola (8 years ago)

We already have that feature in the language: it’s called await. Just rewrite the example like so, instead of using /* pause to await x */ comments:

async function makePizza(sauceType = 'red') {
  let dough  = makeDough();
  let sauce  = await makeSauce(sauceType);
  let cheese = grateCheese(sauce.determineCheese());
  
  dough = await dough;
  dough.add(sauce);
  dough.add(await cheese);
  
  return dough;
}

This way, instead of random punctuation like the "." operator causing your program to await... it's the actual await keyword.

# Šime Vidas (8 years ago)

To clarify, the idea is to declare and kick off all the concurrent tasks upfront (using local variables and the ‘lazy await’ keyword), and then just continue writing the rest of the code ‘as if all the promises are resolved’. The async function automagically pauses whenever needed, so it’s no longer necessary to insert await operators throughout the code.

I admit, this is wishful thinking. I’m just waiting for someone to tell me that it’s not feasible or that it would lead to some very bad code patterns :)

# Logan Smyth (8 years ago)

So you'd be imagining something that would create a variable that would automatically await when accessed, like

async function makePizza(sauceType = 'red') {

  await const dough  = makeDough();
  await const sauce  = makeSauce(sauceType);
  await const cheese = grateCheese(sauce.determineCheese());

  dough.add(sauce);
  dough.add(cheese);

  return dough;
}

that would ensure the value is available before accessing them?

# Danielle McLean (8 years ago)

On 24 February 2017 at 16:19:03, Šime Vidas (sime.vidas at gmail.com) wrote:

To clarify, the idea is to declare and kick off all the concurrent tasks upfront

Well, that's what promises already do, even without using the async and await keywords. You kick off all concurrent tasks up-front - it's only tasks that depend on a previous task's result that need wait around for that task to finish. Without async functions, you'd probably do something like this:

function makePizza(sauceType = 'red') {
  const dough = makeDough(), sauce = makeSauce(sauceType);
  const cheese = sauce.then(s => grateCheese(s.determineCheese()));
  return Promise.all([dough, sauce, cheese]).then(function([dough, sauce, cheese]) {
    dough.add(sauce);
    dough.add(cheese);
    return dough;
  }
}

With async functions, you can avoid all those lambdas and the code's a little bit cleaner - either way, you don't need new JS magic to do things concurrently!

# T.J. Crowder (8 years ago)

On Fri, Feb 24, 2017 at 5:18 AM, Šime Vidas <sime.vidas at gmail.com> wrote:

To clarify, the idea is to declare and kick off all the concurrent tasks upfront (using local variables and the ‘lazy await’ keyword), and then just continue writing the rest of the code ‘as if all the promises are resolved’. The async function automagically pauses whenever needed, so it’s no longer necessary to insert await operators throughout the code.

Not to be a naysayer, but I'm not a big fan of hidden behavior. If I see sauce.determineCheese(), the idea that the . is triggering a behind-the-scenes await isn't attractive. (Granted, there are already plenty of things it could be doing behind the scenes, with getters and proxies; but at least they're not changing the temporal semantics of the statement.) Domenic's version using current async/await syntax is nice and clear (one might tweak the variable names a bit to differentiate promises from resolved values, but...).

Separately, I think you're going to run into implementational complexity. These automatic-await values are neither promises nor resolved values, they're a new beast with hidden await behavior; call them "hidden promises." Within an async function, most but not all GetValue operations on the variables/properties containing these hidden promises would need an "if this is a hidden promise, await it" guard: Any math operation, any string operation, any object operation, any time an async function passes the value into a non-async function, etc. Just about the only exception would be assignment (well, most assignments; more below), which would just copy the hidden promise. This becomes particularly problematic when you think about what it means to have one of these within a structure, like an array or object; what if we then pass its container to a non-async function? Do we recursively search the container for automatic-await values and await them before calling the non-async function? In what order? Similarly, the return values of async functions would need vetting, but (arguably) only if being returned to non-async functions, which makes for some new layer between caller and callee or an uncomfortable awareness in the async function of where its return value is going. Handling all of that sounds like a lot of runtime cost to simply hide await from ourselves. And updating all of this in existing engines seems like a lot of work.

Then there's the question of which assignment operations would need to trigger a hidden await. Presumably not let x = y; where y is a hidden promise, that would largely defeat the purpose. But consider:

let x;

function example() {
    a().then(() => { /* Do something */ });
}
async function a() {
    async const hiddenPromise = getPromise(); // Or whatever the syntax would be...
    x = hiddenPromise;
    return await hiddenPromise;
}
example();
console.log(x); // What does this see?

Finally, there's the educational cost of explaining what triggers a hidden await to new JavaScripters.

I can imagine a new inherently-async language with this sort of thing at its core (probably without non-async functions at all, avoiding a lot of the complexity above; instead making async-until-the-last-second the default with an explicit "resolve"). It could well be quite interesting.

But for JavaScript, I think we're better off with the explicit await.

-- T.J.

# Šime Vidas (8 years ago)

Domenic's version using current async/await syntax is nice and clear (one might tweak the variable names a bit to differentiate promises from resolved values, but...).

This is the issue I have with this approach. The author is forced to create two sets of variables (for promises and resolved values), depending on the structure of the concurrent tasks. I think this is micromanagement. If the language can resolve us of this, I think that’s a convenience worth having.

Separately, I think you're going to run into implementational complexity. These automatic-await values are neither promises nor resolved values, they're a new beast with hidden await behavior; call them "hidden promises." Within an async function, most but not all [GetValue][1] operations on the variables/properties containing these hidden promises would need an "if this is a hidden promise, await it" guard: Any math operation, any string operation, any object operation, any time an async function passes the value into a non-async function, etc. Just about the only exception would be assignment (well, most assignments; more below), which would just copy the hidden promise. This becomes particularly problematic when you think about what it means to have one of these within a structure, like an array or object; what if we then pass its container to a non-async function? Do we recursively search the container for automatic-await values and await them before calling the non-async function? In what order? Similarly, the return values of async functions would need vetting, but (arguably) only if being returned to non-async functions, which makes for some new layer between caller and callee or an uncomfortable awareness in the async function of where its return value is going. Handling all of that sounds like a lot of runtime cost to simply hide await from ourselves. And updating all of this in existing engines seems like a lot of work.

I’m not sure why it would need to be that complicated. These lazy-await variables would resolve to a value on first use, in any context (first use = first appearance of the variable in the code). With that logic, the variables could be treated as resolved values (i.e. the author would view them as values, not promises) - it’s just that they’re lazy-resolved, so instead of blocking early, they block late, allowing multiple concurrent tasks to be kicked off at the beginning of the async function, without having to micromanage their promises. That’s the key, I think - the benefits of concurrent tasks without the burden of managing promises.

Finally, there's the educational cost of explaining what triggers a hidden await to new JavaScripters.

I can imagine a new inherently-async language with this sort of thing at its core (probably without non-async functions at all, avoiding a lot of the complexity above; instead making async-until-the-last-second the default with an explicit "resolve"). It could well be quite interesting.

But for JavaScript, I think we're better off with the explicit await.

In case it’s not clear, this wouldn’t change how await works or be a replacement of it. Await is great, especially for sequential async operations. So, everyone, continue using explicit await. But this new ‘lazy-await variable’ would enable new code patterns - it would allow us to get multiple async values in parallel, without having to micromanage their promises. I think, there’s a real value here.

# Šime Vidas (8 years ago)

Yes, I think you nailed it. I didn’t make the connection before. Instead of awaiting upfront, forcing the async operations to run in sequence, the awaits are ‘moved’ to the variables themselves, allowing the async ops to run in parallel (as much as possible), and once one such variable is used (in any form), the async function makes sure to pause execution in order to wait for the value to resolve.

As I’ve said elsewhere in this thread, we would get optimal concurrency without having to micromanage the promises.

# T.J. Crowder (8 years ago)

On Fri, Feb 24, 2017 at 6:35 PM, Šime Vidas <sime.vidas at gmail.com> wrote:

Domenic's version using current async/await syntax is nice and clear (one might tweak the variable names a bit to differentiate promises from resolved values, but...).

This is the issue I have with this approach. The author is forced to create two sets of variables (for promises and resolved values), depending on the structure of the concurrent tasks. I think this is micromanagement.

Controlling the temporal mechanics of my functions seems pretty macro to me. :-) We just appear to have different perspectives on it, which is fair enough.

Separately, I think you're going to run into implementational complexity. [snip]

I’m not sure why it would need to be that complicated. [snip]

That would be simpler, yes, some kind of flag on the binding in the lexical env object perhaps. It would also be a lot less powerful (though I suppose if you need power, keep the promise). It seems like it would encourage people to prefer large functions to small ones working together, though, so they can avoid passing the hidden promise into/out of a call, triggering the hidden await. Which, again, you could address by keeping the promise and passing it around instead, but then that negates the utility you're suggesting (not managing the process directly). And programmers don't need more inducement to bad habits. (I'm particularly guilty of this particular bad habit. But I'm trying...)

I'm also troubled by how this moves the exception point if the promise rejects, from the nice clear await expression to when the side-effect of use occurs. I suppose that's part of my overall concern about hidden behaviors, though.

In case it’s not clear, this wouldn’t change how await works or be a replacement of it.

Just for the avoidance of doubt: You mean it wouldn't be await, but something await-like but distinct, right? autoawait, await*, lateawait, jitawait, something.

Again, I find it a really interesting idea, perhaps as a foundational concept for a massively-async language. I'm just not sure about it for JavaScript.

Anyway, those are my thoughts, FWIW. I'll lurk for a bit. :-)

-- T.J.

# Marius Gundersen (8 years ago)

Note that you can also await the same promise multiple times, so you could do it like this:

async function makePizza(sauceType = 'red') {

let dough = makeDough(); let sauce = makeSauce(sauceType); let cheese = grateCheese((await sauce).determineCheese());

(await dough).add(await sauce); (await dough).add(await cheese);

return (await dough); }

Now the variables will be awaited on first use

Marius Gundersen

On 24 Feb 2017 03:36, "Šime Vidas" <sime.vidas at gmail.com> wrote:

Daniel Brain from PayPal has written a post about async/await: medium.com/@bluepnume/even-with-async-await-you- probably-still-need-promises-9b259854c161

It revolves around writing an async function which would execute three tasks in parallel like so:

|--------- dough ---------> |---- sauce ----> |-- cheese -->

The code used as a starting point was:

async function makePizza(sauceType = 'red') {

let dough = await makeDough(); let sauce = await makeSauce(sauceType); let cheese = await grateCheese(sauce.determineCheese());

dough.add(sauce); dough.add(cheese);

return dough; }

This pattern, of course, cases the tasks to execute in sequence, like so:

|-------- dough --------> |-------- sauce --------> |-- cheese -->

The remainder of the post introduces several solutions in an attempt to achieve optimal concurrency. For instance, the author’s preferred solution uses a custom memoize function and Promise.all.

Compared to the initial code above, these solutions seem complex, almost as if the language does not have the appropriate syntactic forms and/or APIs to address this particular use case.

This got me thinking. What if there was a version of await that doesn’t pause execution on the spot, but continues execution until the variable which the await is assigned to, is first referenced?

I’ve added comments to mark the positions where execution pauses:

async function makePizza(sauceType = 'red') {

let dough = await makeDough(); let sauce = await makeSauce(sauceType); let cheese = await grateCheese(/* pause to await sauce */ sauce.determineCheese());

/* pause to await dough / dough.add(sauce); dough.add(/ pause to await cheese */ cheese);

return dough; }

Please let me know if adding a await-like keyword that works like this would be a bad idea. From a layman’s perspective, it seems that having this feature would simplify code patterns which involve execution in parallel like the example described in the article.