Conditional await, anyone?

classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

Conditional await, anyone?

Andrea Giammarchi-2
When developers use `async` functions, they'll likely also await unconditionally any callback, even if such callback could return a non promise value, for whatever reason.

Engines are great at optimizing stuff, but as of today, if we measure the performance difference between this code:

```js
(async () => {
  console.time('await');
  const result = await (async () => [await 1, await 2, await 3])();
  console.timeEnd('await');
  return result;
})();
```

and the following one:

```js
(/* sync */ () => {
  console.time('sync');
  const result = (() => [1, 2, 3])();
  console.timeEnd('sync');
  return result;
})();
```

we'll notice the latter is about 10 to 100 times faster than the asynchronous one.

Sure thing, engines might infer returned values in some hot code and skip the microtask dance once it's sure some callback might return values that are not promises, but what if developers could give hints about this possibility?

In a twitter exchange, one dev mentioned the following:

> I often write:
> let value = mightBePromise()
> if (value && value.then)
>   value = await value
> in hot code in my async functions, for precisely this reason (I too have measured the large perf difference).

so ... how about accepting a question mark after `await` so that it's clear the developer knows the function might return non Promise based results, hence the code could be faster?

```js
const value = await? callback();
```

Above code is basically what's this thread/proposal about, adding a conditional `await` syntax, so that if the returned value is not thenable, there's no reason to waste a microtask on it.

What are your thoughts?



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Gus Caplan
> Sure thing, engines might infer returned values in some hot code and skip the microtask dance once it's sure some callback might return values that are not promises, but what if developers could give hints about this possibility?

Engines can't do this, because it would change the observable order of running code.

> so ... how about accepting a question mark after `await` so that it's clear the developer knows the function might return non Promise based results, hence the code could be faster?

This is actually considered an antipattern (google "zalgo javascript"), and promises try to stop that from happening.

On Tue, Oct 8, 2019 at 10:34 AM Andrea Giammarchi <[hidden email]> wrote:
When developers use `async` functions, they'll likely also await unconditionally any callback, even if such callback could return a non promise value, for whatever reason.

Engines are great at optimizing stuff, but as of today, if we measure the performance difference between this code:

```js
(async () => {
  console.time('await');
  const result = await (async () => [await 1, await 2, await 3])();
  console.timeEnd('await');
  return result;
})();
```

and the following one:

```js
(/* sync */ () => {
  console.time('sync');
  const result = (() => [1, 2, 3])();
  console.timeEnd('sync');
  return result;
})();
```

we'll notice the latter is about 10 to 100 times faster than the asynchronous one.

Sure thing, engines might infer returned values in some hot code and skip the microtask dance once it's sure some callback might return values that are not promises, but what if developers could give hints about this possibility?

In a twitter exchange, one dev mentioned the following:

> I often write:
> let value = mightBePromise()
> if (value && value.then)
>   value = await value
> in hot code in my async functions, for precisely this reason (I too have measured the large perf difference).

so ... how about accepting a question mark after `await` so that it's clear the developer knows the function might return non Promise based results, hence the code could be faster?

```js
const value = await? callback();
```

Above code is basically what's this thread/proposal about, adding a conditional `await` syntax, so that if the returned value is not thenable, there's no reason to waste a microtask on it.

What are your thoughts?


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Tab Atkins Jr.
In reply to this post by Andrea Giammarchi-2
I'm not sure I understand the intended use-case here.  If the author
knows the function they're calling is async, they can use `await`
normally. If they know it's not async, they can avoid `await`
altogether. If they have no idea whether it's async or not, that means
they just don't understand what the function is returning, which
sounds like a really bad thing that they should fix?  And in that
case, as Gus says, `await?`'s semantics would do some confusing things
to execution order, making the line after the `await?` either run
*before* or *after* the calling code, depending on whether the await'd
value was a promise or not.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Dan Peddle
Have to agree, mixing sync and async code like this looks like a disaster waiting to happen. Knowing which order your code will be executed in might seem not so important for controlled environments where micro optimisations are attractive, but thinking about trying to track down a bug through this would drive me nuts.

Imagine you have a cached value which can be retrieved synchronously - other code which runs in order, and perhaps not directly part of this chain, would be fine. When it’s not there, zalgo would indeed be released. The solution to this is to use promises (as I’m sure you know) so you have a consistent way of saying when something is ready... otherwise it’s thenable sniffing all the way through the codebase.

Async infers some kind of IO or deferred process, scheduling. If there’s a dependency, then we need to express that. That it may be in some cases available synchronously seems like something to be extremely wary of.

> On 8. Oct 2019, at 22:25, Tab Atkins Jr. <[hidden email]> wrote:
>
> I'm not sure I understand the intended use-case here.  If the author
> knows the function they're calling is async, they can use `await`
> normally. If they know it's not async, they can avoid `await`
> altogether. If they have no idea whether it's async or not, that means
> they just don't understand what the function is returning, which
> sounds like a really bad thing that they should fix?  And in that
> case, as Gus says, `await?`'s semantics would do some confusing things
> to execution order, making the line after the `await?` either run
> *before* or *after* the calling code, depending on whether the await'd
> value was a promise or not.
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Andrea Giammarchi-2
I don't know why this went in a completely unrelated direction so ... I'll try to explain again what is `await?` about.

My first two examples show a relevant performance difference between the async code and the sync one.

The async code though, has zero reasons to be async and so much slower.

```js
(async () => {
  console.time('await');
  const result = await (async () => [await 1, await 2, await 3])();
  console.timeEnd('await');
  return result;
})();
```

Why would `await 1` ever need to create a micro task, if already executed into a scheduled one via async?

Or in general, why any callback that would early return a value that is not a promise should create a micro task?

So the proposal was implemented in an attempt to de-sugar `await?` into the steps proposed by the dev I've interacted with:

```js
const value = await? callback();

// as sugar for
let value = callback();
if ('then' in value)
  value = await value;
```

The order is guaranteed and linear in every case, so that nothing actually change logically speaking, and the hint would be about performance, 'cause engines don't apparently optimize non-promise based cases.

However, since the initial intent/proposal about performance got translated into everything else, I've instantly lost interest myself as it's evident an `await?` would causes more confusion than it solves.

I am also not answering other points as not relevant for this idea/proposal.

Thanks regardless for sharing your thoughts, it helped me see it would confuse developers.

Best Regards





On Tue, Oct 8, 2019 at 11:58 PM Dan Peddle <[hidden email]> wrote:
Have to agree, mixing sync and async code like this looks like a disaster waiting to happen. Knowing which order your code will be executed in might seem not so important for controlled environments where micro optimisations are attractive, but thinking about trying to track down a bug through this would drive me nuts.

Imagine you have a cached value which can be retrieved synchronously - other code which runs in order, and perhaps not directly part of this chain, would be fine. When it’s not there, zalgo would indeed be released. The solution to this is to use promises (as I’m sure you know) so you have a consistent way of saying when something is ready... otherwise it’s thenable sniffing all the way through the codebase.

Async infers some kind of IO or deferred process, scheduling. If there’s a dependency, then we need to express that. That it may be in some cases available synchronously seems like something to be extremely wary of.

> On 8. Oct 2019, at 22:25, Tab Atkins Jr. <[hidden email]> wrote:
>
> I'm not sure I understand the intended use-case here.  If the author
> knows the function they're calling is async, they can use `await`
> normally. If they know it's not async, they can avoid `await`
> altogether. If they have no idea whether it's async or not, that means
> they just don't understand what the function is returning, which
> sounds like a really bad thing that they should fix?  And in that
> case, as Gus says, `await?`'s semantics would do some confusing things
> to execution order, making the line after the `await?` either run
> *before* or *after* the calling code, depending on whether the await'd
> value was a promise or not.
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Isiah Meadows-2
I had a similar example in real-world code, but it was just to merge
sync and async into the same code path. I handled it by using
generators and basically running them myself:
https://github.com/isiahmeadows/mithril-node-render/blob/v2/index.js#L195-L206

In either case, I'm not sure it's worth adding new syntax for it.

-----

Isiah Meadows
[hidden email]
www.isiahmeadows.com

On Wed, Oct 9, 2019 at 3:08 AM Andrea Giammarchi
<[hidden email]> wrote:

>
> I don't know why this went in a completely unrelated direction so ... I'll try to explain again what is `await?` about.
>
> My first two examples show a relevant performance difference between the async code and the sync one.
>
> The async code though, has zero reasons to be async and so much slower.
>
> ```js
> (async () => {
>   console.time('await');
>   const result = await (async () => [await 1, await 2, await 3])();
>   console.timeEnd('await');
>   return result;
> })();
> ```
>
> Why would `await 1` ever need to create a micro task, if already executed into a scheduled one via async?
>
> Or in general, why any callback that would early return a value that is not a promise should create a micro task?
>
> So the proposal was implemented in an attempt to de-sugar `await?` into the steps proposed by the dev I've interacted with:
>
> ```js
> const value = await? callback();
>
> // as sugar for
> let value = callback();
> if ('then' in value)
>   value = await value;
> ```
>
> The order is guaranteed and linear in every case, so that nothing actually change logically speaking, and the hint would be about performance, 'cause engines don't apparently optimize non-promise based cases.
>
> However, since the initial intent/proposal about performance got translated into everything else, I've instantly lost interest myself as it's evident an `await?` would causes more confusion than it solves.
>
> I am also not answering other points as not relevant for this idea/proposal.
>
> Thanks regardless for sharing your thoughts, it helped me see it would confuse developers.
>
> Best Regards
>
>
>
>
>
> On Tue, Oct 8, 2019 at 11:58 PM Dan Peddle <[hidden email]> wrote:
>>
>> Have to agree, mixing sync and async code like this looks like a disaster waiting to happen. Knowing which order your code will be executed in might seem not so important for controlled environments where micro optimisations are attractive, but thinking about trying to track down a bug through this would drive me nuts.
>>
>> Imagine you have a cached value which can be retrieved synchronously - other code which runs in order, and perhaps not directly part of this chain, would be fine. When it’s not there, zalgo would indeed be released. The solution to this is to use promises (as I’m sure you know) so you have a consistent way of saying when something is ready... otherwise it’s thenable sniffing all the way through the codebase.
>>
>> Async infers some kind of IO or deferred process, scheduling. If there’s a dependency, then we need to express that. That it may be in some cases available synchronously seems like something to be extremely wary of.
>>
>> > On 8. Oct 2019, at 22:25, Tab Atkins Jr. <[hidden email]> wrote:
>> >
>> > I'm not sure I understand the intended use-case here.  If the author
>> > knows the function they're calling is async, they can use `await`
>> > normally. If they know it's not async, they can avoid `await`
>> > altogether. If they have no idea whether it's async or not, that means
>> > they just don't understand what the function is returning, which
>> > sounds like a really bad thing that they should fix?  And in that
>> > case, as Gus says, `await?`'s semantics would do some confusing things
>> > to execution order, making the line after the `await?` either run
>> > *before* or *after* the calling code, depending on whether the await'd
>> > value was a promise or not.
>> >
>> > ~TJ
>> > _______________________________________________
>> > es-discuss mailing list
>> > [hidden email]
>> > https://mail.mozilla.org/listinfo/es-discuss
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Herby Vojčík
In reply to this post by Tab Atkins Jr.
Hi!

First, experiences of this guy
https://medium.com/@bluepnume/intentionally-unleashing-zalgo-with-promises-ab3f63ead2fd 
seem to refute the problematicity of zalgo.

Second, I actually have the use case of this pattern (though actually
it's not a use case for a new syntax). In Amber Smalltalk
(implementation running of top of JS engine), it would immensely help to
be able to have classical "proxied message send" case, where message
send is asynchronous in the background. As Amber compiles the code
itself, you may say "compile it so you simply await every message send",
but then, code that _must be_ synchronous (callbacks used in external JS
libs API) will fail. Having two modes is not an option. So I would
really use the option to have message sends synchronous by default, but
in the case they _can be_ asynchronous, to be able to let them be that way.

Actually, the paypal guy mentioned in the first paragraph has similar
case (inter-frame RPC). Using his ZalgoPromise I could compile the
things like

   ZalgoPromise.resolve(make the message send).then(function (result) ...

Herby

On 8. 10. 2019 22:25, Tab Atkins Jr. wrote:

> I'm not sure I understand the intended use-case here.  If the author
> knows the function they're calling is async, they can use `await`
> normally. If they know it's not async, they can avoid `await`
> altogether. If they have no idea whether it's async or not, that means
> they just don't understand what the function is returning, which
> sounds like a really bad thing that they should fix?  And in that
> case, as Gus says, `await?`'s semantics would do some confusing things
> to execution order, making the line after the `await?` either run
> *before* or *after* the calling code, depending on whether the await'd
> value was a promise or not.
>
> ~TJ
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
>

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Tab Atkins Jr.
In reply to this post by Andrea Giammarchi-2
On Wed, Oct 9, 2019 at 12:08 AM Andrea Giammarchi
<[hidden email]> wrote:
> I don't know why this went in a completely unrelated direction so ... I'll try to explain again what is `await?` about.

Nah, we got it. Our complaint was still about the semantics.

> ```js
> const value = await? callback();
>
> // as sugar for
> let value = callback();
> if ('then' in value)
>   value = await value;
> ```
>
> The order is guaranteed and linear in every case, so that nothing actually change logically speaking, and the hint would be about performance, 'cause engines don't apparently optimize non-promise based cases.

Expand that code so there's a caller:

```js
function one() {
  two();
  console.log("one");
}
async function two() {
  await? maybeAsync();
  console.log("two");
}
```

What's the order of the logs?

If maybeAsync() is synchronous, then one() calls two(), two() calls
maybeAsync() which returns immediately, and it continues to log "two"
before ending and returning execution to one(), which then logs "one"
and ends.

If maybeAsync() returns a promise, then one() calls two(), two calls
maybeAsync() then freezes while it waits for the promise to resolve,
returning execution to one(). Since one() isn't awaiting the promise
returned by two(), it just immediately continues and logs "one" then
ends. At some point later in execution, the maybeAsync() promise
resolves, two() unfreezes, then it logs "two".

So no, the order is not guaranteed. It's unpredictable and depends on
whether the function being await?'d returns a promise or not. If you
don't know what maybeAsync() returns, you won't be able to predict
your own execution flow, which is dangerous and a very likely source
of bugs.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Andrea Giammarchi-2
> What's the order of the logs?

Exactly the same, as the `await?` is inevitably inside an `async` function which would grant a single microtask instead of N.

Example:

```js
async function tasks() {
  await? maybeAsync();
  await? maybeAsync();
  await? maybeAsync();
}

tasks();
```

If `maybeAsync` returns twice non promises results, there is only one microtask within the async `tasks` function, that would linearly collect all non promises, so that above example could have 1, 2, max 4 microtasks, instead of always 4
.
To explain `await?` in steps:

  * is the awaited value a promise? schedule microtask
  * otherwise schedule a single microtask if none has been scheduled already, or queue this result to the previous scheduled one

This would grant same linear order and save time.

However, like others said already in the twitter thread, we all wish `await` was already working like that by default, while it seems to unconditionally create micro tasks even when it's not strictly necessary.




On Wed, Oct 9, 2019 at 11:47 PM Tab Atkins Jr. <[hidden email]> wrote:
On Wed, Oct 9, 2019 at 12:08 AM Andrea Giammarchi
<[hidden email]> wrote:
> I don't know why this went in a completely unrelated direction so ... I'll try to explain again what is `await?` about.

Nah, we got it. Our complaint was still about the semantics.

> ```js
> const value = await? callback();
>
> // as sugar for
> let value = callback();
> if ('then' in value)
>   value = await value;
> ```
>
> The order is guaranteed and linear in every case, so that nothing actually change logically speaking, and the hint would be about performance, 'cause engines don't apparently optimize non-promise based cases.

Expand that code so there's a caller:

```js
function one() {
  two();
  console.log("one");
}
async function two() {
  await? maybeAsync();
  console.log("two");
}
```

What's the order of the logs?

If maybeAsync() is synchronous, then one() calls two(), two() calls
maybeAsync() which returns immediately, and it continues to log "two"
before ending and returning execution to one(), which then logs "one"
and ends.

If maybeAsync() returns a promise, then one() calls two(), two calls
maybeAsync() then freezes while it waits for the promise to resolve,
returning execution to one(). Since one() isn't awaiting the promise
returned by two(), it just immediately continues and logs "one" then
ends. At some point later in execution, the maybeAsync() promise
resolves, two() unfreezes, then it logs "two".

So no, the order is not guaranteed. It's unpredictable and depends on
whether the function being await?'d returns a promise or not. If you
don't know what maybeAsync() returns, you won't be able to predict
your own execution flow, which is dangerous and a very likely source
of bugs.

~TJ

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Tab Atkins Jr.
On Wed, Oct 9, 2019 at 11:17 PM Andrea Giammarchi
<[hidden email]> wrote:
> > What's the order of the logs?
>
> Exactly the same, as the `await?` is inevitably inside an `async` function which would grant a single microtask instead of N.

I think you're misreading my example?  Check this out:
http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7271

```html
<!DOCTYPE html>
<script>
function one() {
  oneAsync();
  w("one A");
}
async function oneAsync() {
  await Promise.resolve();
  w("one B");
}

function two() {
  twoAsync();
  w("two A");
}

async function twoAsync() {
  // await? true;
  w("two B");
}

one();
two();
</script>
```

This script logs:

```
log: one A
log: two B
log: two A
log: one B
```

A and B are logged in different order depends on whether there's an
`await` creating a microtask checkpoint or not. `await? true` won't
create a microtask checkpoint, so you'll get the two() behavior,
printing B then A. `await? Promise.resolve(true)` will create one, so
you'll get the one() behavior, printing A then B.

> If `maybeAsync` returns twice non promises results, there is only one microtask within the async `tasks` function, that would linearly collect all non promises, so that above example could have 1, 2, max 4 microtasks, instead of always 4
> .
> To explain `await?` in steps:
>
>   * is the awaited value a promise? schedule microtask
>   * otherwise schedule a single microtask if none has been scheduled already, or queue this result to the previous scheduled one
>
> This would grant same linear order and save time.

Ah, your earlier posts didn't say that `await? nonPromise` *would*
schedule a microtask in some cases! That does change things.  Hm, I
wonder if this is observably different from the current behavior?

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Andrea Giammarchi-2
Again, the `await?` is sugar for the following:

```js
const value = await? callback();

// as sugar for
let value = callback();
if ('then' in value)
  value = await value;
```

but since I've stated already I have no interest anymore in this proposal, we can also stop explaining to each others things we know already.

Best Regards

On Fri, Oct 11, 2019 at 1:32 AM Tab Atkins Jr. <[hidden email]> wrote:
On Wed, Oct 9, 2019 at 11:17 PM Andrea Giammarchi
<[hidden email]> wrote:
> > What's the order of the logs?
>
> Exactly the same, as the `await?` is inevitably inside an `async` function which would grant a single microtask instead of N.

I think you're misreading my example?  Check this out:
http://software.hixie.ch/utilities/js/live-dom-viewer/saved/7271

```html
<!DOCTYPE html>
<script>
function one() {
  oneAsync();
  w("one A");
}
async function oneAsync() {
  await Promise.resolve();
  w("one B");
}

function two() {
  twoAsync();
  w("two A");
}

async function twoAsync() {
  // await? true;
  w("two B");
}

one();
two();
</script>
```

This script logs:

```
log: one A
log: two B
log: two A
log: one B
```

A and B are logged in different order depends on whether there's an
`await` creating a microtask checkpoint or not. `await? true` won't
create a microtask checkpoint, so you'll get the two() behavior,
printing B then A. `await? Promise.resolve(true)` will create one, so
you'll get the one() behavior, printing A then B.

> If `maybeAsync` returns twice non promises results, there is only one microtask within the async `tasks` function, that would linearly collect all non promises, so that above example could have 1, 2, max 4 microtasks, instead of always 4
> .
> To explain `await?` in steps:
>
>   * is the awaited value a promise? schedule microtask
>   * otherwise schedule a single microtask if none has been scheduled already, or queue this result to the previous scheduled one
>
> This would grant same linear order and save time.

Ah, your earlier posts didn't say that `await? nonPromise` *would*
schedule a microtask in some cases! That does change things.  Hm, I
wonder if this is observably different from the current behavior?

~TJ

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Tab Atkins Jr.
On Fri, Oct 11, 2019 at 1:15 AM Andrea Giammarchi
<[hidden email]> wrote:

> Again, the `await?` is sugar for the following:
>
> ```js
> const value = await? callback();
>
> // as sugar for
> let value = callback();
> if ('then' in value)
>   value = await value;
> ```

Okay, so that has the "you can't predict execution order any more"
problem. But that's not consistent with what you said in "otherwise
schedule a single microtask if none has been scheduled already, or
queue this result to the previous scheduled one", which implies a
different desugaring:

```js
let value = callback();
if('then' in value || thisFunctionHasntAwaitedYet)
  value = await value;
```

*This* desugaring has a consistent execution order, and still meets
your goal of "don't add a bunch of microtask checkpoints for
synchronous values".

Put another way, this `await?` is equivalent to `await` *if* you're
still in the "synchronously execute until you hit the first await"
phase of executing an async function; but it's equivalent to your
simpler desugaring ("await only if this is a thenable") after that.

> but since I've stated already I have no interest anymore in this proposal, we can also stop explaining to each others things we know already.

I'm fine if you want to drop it, but we're not explaining things we
already know to each other. At least one of us is confused about
what's being proposed. And the altered desugaring I give up above at
least has a chance of happening; the only issue might be the
observability of how many microtasks get scheduled. If that's not a
problem, it might be possible to suggest this as an actual change to
how `await` works.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Andrea Giammarchi-2
in order to work, `await` must be executed in an async scope/context (either top level or within a closure).

In such case, either somebody is awaiting the result of that `async` execution, or the order doesn't matter.

The following two examples produce indeed the very same result:

```js
(async () => { return 1; })().then(console.log);
console.log(2);

(async () => { return await 1; })().then(console.log);
console.log(2);
```

Except the second one will be scheduled a micro task too far.

Since nobody counts the amount of microtasks per async function execution, the result is practically the same, except the second example is always slower.

Putting all together:

```js
(async () => { return await 'third'; })().then(console.log);
(async () => { return 'second'; })().then(console.log);
console.log('first');
```

If you `await` the return of an async function, you are consuming that microtask regardless, which is what `await?` here would like to avoid: do not create a micro task when it's not necessary.

There's no footgun as the `await?` is an explicit intent from the developer, so if the developer knows what s/he's doing, can use `await?`, otherwise if the order of the microtask matters at all, can always just use `await`.

As summary: the proposal was to help engines be faster when it's possible, but devs are confused by the syntax, and maybeat the end there wouldn't be as many benefits compared to the apparent confusion this proposal would add.

I hope I've explained properly what was this about.

Regards


On Fri, Oct 11, 2019 at 10:43 PM Tab Atkins Jr. <[hidden email]> wrote:
On Fri, Oct 11, 2019 at 1:15 AM Andrea Giammarchi
<[hidden email]> wrote:
> Again, the `await?` is sugar for the following:
>
> ```js
> const value = await? callback();
>
> // as sugar for
> let value = callback();
> if ('then' in value)
>   value = await value;
> ```

Okay, so that has the "you can't predict execution order any more"
problem. But that's not consistent with what you said in "otherwise
schedule a single microtask if none has been scheduled already, or
queue this result to the previous scheduled one", which implies a
different desugaring:

```js
let value = callback();
if('then' in value || thisFunctionHasntAwaitedYet)
  value = await value;
```

*This* desugaring has a consistent execution order, and still meets
your goal of "don't add a bunch of microtask checkpoints for
synchronous values".

Put another way, this `await?` is equivalent to `await` *if* you're
still in the "synchronously execute until you hit the first await"
phase of executing an async function; but it's equivalent to your
simpler desugaring ("await only if this is a thenable") after that.

> but since I've stated already I have no interest anymore in this proposal, we can also stop explaining to each others things we know already.

I'm fine if you want to drop it, but we're not explaining things we
already know to each other. At least one of us is confused about
what's being proposed. And the altered desugaring I give up above at
least has a chance of happening; the only issue might be the
observability of how many microtasks get scheduled. If that's not a
problem, it might be possible to suggest this as an actual change to
how `await` works.

~TJ

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Tab Atkins Jr.
On Sat, Oct 12, 2019 at 7:19 AM Andrea Giammarchi
<[hidden email]> wrote:
> in order to work, `await` must be executed in an async scope/context (either top level or within a closure).
>
> In such case, either somebody is awaiting the result of that `async` execution, or the order doesn't matter.

That's definitely not true. I gave you an explicit example where the
order differs. That example code is realistic if you're using the
async call for side-effects only (and thus don't care about the
returned promise), or if you're storing the returned promise in a
variable so you can pass it to one of the promise combinators later.
In either of these cases the order of execution between the sync and
async code can definitely matter.

> The following two examples produce indeed the very same result:
>
> ```js
> (async () => { return 1; })().then(console.log);
> console.log(2);
>
> (async () => { return await 1; })().then(console.log);
> console.log(2);
> ```

In both of these cases, you're doing no additional work after the
"maybe async" point. That is the exact part that moves in execution
order between the two cases, so obviously you won't see any
difference.  Here's a slightly altered version that shows off the
difference:

```js
(async () => { 1; console.log("async"); return 3; })().then(console.log);
console.log(2);

(async () => { await 1; console.log("async"); return 3; })().then(console.log);
console.log(2);
```

In the first you'll log "async", "2", "3". In the second you'll log
"2", "async", "3".

> As summary: the proposal was to help engines be faster when it's possible, but devs are confused by the syntax, and maybeat the end there wouldn't be as many benefits compared to the apparent confusion this proposal would add.

You still seem to be misunderstanding what the execution order
difference is about. Nobody's confused about the syntax; it's clear
enough. It just does bad, confusing things as you've presented it.

As I said in earlier message, there *is* a way to eliminate the
execution-order difference (making it so the only difference would be
the number of microtasks when your function awaits *multiple* sync
values), which I thought you'd come up with at some point, but I'm
pretty sure it was just me misunderstanding what you'd said.

~TJ
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

Andrea Giammarchi-2
> You still seem to be misunderstanding what the execution order difference is about.

If to stop this thread you need me to say I am confused about anything then fine, "I am confused", but if you keep changing my examples to make your point then this conversation goes nowhere, so I am officially out of this thread.

Best Regards.

On Mon, Oct 14, 2019 at 10:41 PM Tab Atkins Jr. <[hidden email]> wrote:
On Sat, Oct 12, 2019 at 7:19 AM Andrea Giammarchi
<[hidden email]> wrote:
> in order to work, `await` must be executed in an async scope/context (either top level or within a closure).
>
> In such case, either somebody is awaiting the result of that `async` execution, or the order doesn't matter.

That's definitely not true. I gave you an explicit example where the
order differs. That example code is realistic if you're using the
async call for side-effects only (and thus don't care about the
returned promise), or if you're storing the returned promise in a
variable so you can pass it to one of the promise combinators later.
In either of these cases the order of execution between the sync and
async code can definitely matter.

> The following two examples produce indeed the very same result:
>
> ```js
> (async () => { return 1; })().then(console.log);
> console.log(2);
>
> (async () => { return await 1; })().then(console.log);
> console.log(2);
> ```

In both of these cases, you're doing no additional work after the
"maybe async" point. That is the exact part that moves in execution
order between the two cases, so obviously you won't see any
difference.  Here's a slightly altered version that shows off the
difference:

```js
(async () => { 1; console.log("async"); return 3; })().then(console.log);
console.log(2);

(async () => { await 1; console.log("async"); return 3; })().then(console.log);
console.log(2);
```

In the first you'll log "async", "2", "3". In the second you'll log
"2", "async", "3".

> As summary: the proposal was to help engines be faster when it's possible, but devs are confused by the syntax, and maybeat the end there wouldn't be as many benefits compared to the apparent confusion this proposal would add.

You still seem to be misunderstanding what the execution order
difference is about. Nobody's confused about the syntax; it's clear
enough. It just does bad, confusing things as you've presented it.

As I said in earlier message, there *is* a way to eliminate the
execution-order difference (making it so the only difference would be
the number of microtasks when your function awaits *multiple* sync
values), which I thought you'd come up with at some point, but I'm
pretty sure it was just me misunderstanding what you'd said.

~TJ

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: Conditional await, anyone?

#!/JoePea
I haven't read the whole thread, but I also disliked the ugly
conditional checking I had to do to get faster results.

I agree that this would changed perceived code execution order
(unexpectedly, a breaking change).

I believe that a better solution would be to make it explicit in the
function definition, so that it carries up into caller code:

```js
async? function foo() { ... }
```

Now, the user of the function must be required to use `await?` and
thus has to be forced to think that the `foo` function might possibly
run async, or might not. Using `await` on an `async? function` would
be a runtime error, to further prevent possible confusion and errors.

Only with a construct like `async? function` could I see this becoming
possible, so that there aren't any surprising breaking changes in
downstream projects.

On Tue, Oct 15, 2019 at 12:26 AM Andrea Giammarchi

<[hidden email]> wrote:

>
> > You still seem to be misunderstanding what the execution order difference is about.
>
> If to stop this thread you need me to say I am confused about anything then fine, "I am confused", but if you keep changing my examples to make your point then this conversation goes nowhere, so I am officially out of this thread.
>
> Best Regards.
>
> On Mon, Oct 14, 2019 at 10:41 PM Tab Atkins Jr. <[hidden email]> wrote:
>>
>> On Sat, Oct 12, 2019 at 7:19 AM Andrea Giammarchi
>> <[hidden email]> wrote:
>> > in order to work, `await` must be executed in an async scope/context (either top level or within a closure).
>> >
>> > In such case, either somebody is awaiting the result of that `async` execution, or the order doesn't matter.
>>
>> That's definitely not true. I gave you an explicit example where the
>> order differs. That example code is realistic if you're using the
>> async call for side-effects only (and thus don't care about the
>> returned promise), or if you're storing the returned promise in a
>> variable so you can pass it to one of the promise combinators later.
>> In either of these cases the order of execution between the sync and
>> async code can definitely matter.
>>
>> > The following two examples produce indeed the very same result:
>> >
>> > ```js
>> > (async () => { return 1; })().then(console.log);
>> > console.log(2);
>> >
>> > (async () => { return await 1; })().then(console.log);
>> > console.log(2);
>> > ```
>>
>> In both of these cases, you're doing no additional work after the
>> "maybe async" point. That is the exact part that moves in execution
>> order between the two cases, so obviously you won't see any
>> difference.  Here's a slightly altered version that shows off the
>> difference:
>>
>> ```js
>> (async () => { 1; console.log("async"); return 3; })().then(console.log);
>> console.log(2);
>>
>> (async () => { await 1; console.log("async"); return 3; })().then(console.log);
>> console.log(2);
>> ```
>>
>> In the first you'll log "async", "2", "3". In the second you'll log
>> "2", "async", "3".
>>
>> > As summary: the proposal was to help engines be faster when it's possible, but devs are confused by the syntax, and maybeat the end there wouldn't be as many benefits compared to the apparent confusion this proposal would add.
>>
>> You still seem to be misunderstanding what the execution order
>> difference is about. Nobody's confused about the syntax; it's clear
>> enough. It just does bad, confusing things as you've presented it.
>>
>> As I said in earlier message, there *is* a way to eliminate the
>> execution-order difference (making it so the only difference would be
>> the number of microtasks when your function awaits *multiple* sync
>> values), which I thought you'd come up with at some point, but I'm
>> pretty sure it was just me misunderstanding what you'd said.
>>
>> ~TJ
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss