Proposal: `await.all {...}` for parallelism

manuelbarzi manuelbarzi at gmail.com
Mon Nov 25 14:56:55 UTC 2019


another example, that may "normalize" it a bit more:

```
async { // ... = returns a promise
    const x1 = await|| ...
    const x2 = await ... (x1)
    const x3 = await|| ...
    const x10 = await ... (x2, x3)

    let x4, x5, x6

    async {
        x4 = await|| ... (x1, x2)
        x5 = await|| ... (x2, x3)
        x6 = await ... (x4, x5, x10)
    }

    let x7, x8, x9

    async {
        x7 = await|| ... (x4, x6)
        x8 = await ... (x6, x7)
        x9 = await|| ... (x5, x6)
    }

    await ... (x8, x9)
}
```

On Mon, Nov 25, 2019 at 3:19 PM manuelbarzi <manuelbarzi at gmail.com> wrote:

> why not making it work with the addition of a new keyword suffix for
> parallel awaits (for example, `||`), grouping the mid blocks that require
> parallelism. playing with your example and going a bit further:
>
> ```
> async {
>   const v0 = await|| returnsAPromise(); // to be grouped in parallel
>   for (let i = 0; (i < 100000); i++) {
>     doesSimpleFastSynchronousMath();
>   }
>   const v1 = await|| returnsAnotherPromise(); // to be grouped in parallel
>   async {
>       await returnsAnotherPromise1();
>       const v2 = await|| returnsAnotherPromise2(); // to be grouped in
> parallel
>       const v3 = await|| returnsAnotherPromise3(); // to be grouped in
> parallel
>       await returnsAnotherPromise4(v2, v3);
>       const v4 = await returnsAnotherPromise5();
>   }
>   await returnsAnotherPromiseX(v0, v1);
> }
> ```
>
> On Mon, Nov 25, 2019 at 2:06 PM Tom Boutell <tom at apostrophecms.com> wrote:
>
>> There is however a performance concern with your code that we should talk
>> about.
>>
>> If I write this:
>>
>> await.all {
>>   returnsAPromise();
>>   for (let i = 0; (i < 100000); i++) {
>>     doesSimpleFastSynchronousMath();
>>   }
>>   returnsAnotherPromise();
>> }
>>
>> Then Babel will have no choice but to compile this to:
>>
>> {
>>   const promises = [];
>>   {
>>     const maybeThenable = returnsAPromise();
>>     if (maybeThenable && maybeThenable.then) {
>>       promises.push(maybeThenable);
>>     }
>>   }
>>   promises.push(returnsAPromise());
>>   for (let i = 0; (i < 100000); i++) {
>>     const maybeThenable = doesSimpleFastSynchronousMath();
>>     if (maybeThenable && maybeThenable.then) {
>>       promises.push(maybeThenable);
>>     }
>>   }
>>   const maybeThenable = returnsAnotherPromise();
>>   if (maybeThenable && maybeThenable.then) {
>>     promises.push(maybeThenable);
>>   }
>> }
>> await Promise.all(promises);
>>
>> Which could have a significant performance impact on that synchronous
>> inner loop.
>>
>>
>>
>> On Mon, Nov 25, 2019 at 7:55 AM Tom Boutell <tom at apostrophecms.com>
>> wrote:
>>
>>> Hey, you're absolutely right! It's OK because it just means things are
>>> more deterministic before the block exits. It doesn't impact any reasonable
>>> expectations *during* the block.
>>>
>>> I am convinced that your syntax is useful and does not introduce any new
>>> confusion.
>>>
>>> I wonder, then, if it is also possible to implement concurrency limits
>>> for this properly?
>>>
>>> await.all({ concurrency: 5 }) {
>>>   for (const item of items) {
>>>     // returns promise
>>>     item.process();
>>>   }
>>> }
>>>
>>> This is more challenging because, in our transpilation, we can't just
>>> bottle up all the promises and call Promise.all at the end. It would be too
>>> late to manage how many are in process at once, bashing on various API
>>> limits (:
>>>
>>> On Sun, Nov 24, 2019 at 7:43 PM Naveen Chawla <naveen.chwl at gmail.com>
>>> wrote:
>>>
>>>> Hi! It does not change the meaning of the ";" at all. As you may
>>>> already know, omitting `await` already invokes multiple async function
>>>> calls in parallel, in current JavaScript, so absolutely no change in that
>>>> respect.
>>>>
>>>> The only thing this `await.all` suggestion does, is ensure that all
>>>> non-awaited async function calls are completed before proceeding beyond the
>>>> end of the block.
>>>>
>>>> i.e. it adds fairly straightforward and terse deterministic control to
>>>> otherwise non-deterministic code, without requiring knowledge of
>>>> destructuring or `Promise.all`.
>>>>
>>>> On Sat, 23 Nov 2019 at 13:25, Tom Boutell <tom at apostrophecms.com>
>>>> wrote:
>>>>
>>>>> This is very interesting, but this code:
>>>>>
>>>>> await.all {
>>>>>    x = getXAsync();
>>>>>    y = getYAsync();
>>>>> }
>>>>>
>>>>> processXAndY(x, y);
>>>>>
>>>>> Still carries within it the problem that if I'm looking at just the
>>>>> middle of the { ... } block — if "await.all" has scrolled offscreen — I'll
>>>>> be completely wrong about what ";" means. I think that's too much magic.
>>>>>
>>>>> Also, in the case of the "for" loop, this doesn't address managing the
>>>>> level of concurrency. Although it could in theory with a syntax like
>>>>> await.all({ concurrency: 5 }), I'm not sure if it's practical to implement
>>>>> that for your general case.
>>>>>
>>>>> Actually I'm curious about what the implementation would look like in
>>>>> general.  If it were babel compiling this, I guess it would have to wrap
>>>>> every statement not preceded by "await" with a check for whether it returns
>>>>> a thenable and add it to an array if it does. But with the concurrency
>>>>> feature it would also have to defer executing the code at all until the
>>>>> right time as otherwise we're still starting zillions of "processes" at
>>>>> once.
>>>>>
>>>>>
>>>>> On Sat, Nov 23, 2019 at 5:08 AM Naveen Chawla <naveen.chwl at gmail.com>
>>>>> wrote:
>>>>>
>>>>>> However, if `await.all { ... }` were to mean "wait for all
>>>>>> non-awaited async function calls made within this block to complete before
>>>>>> proceeding", as I suggested earlier, I think that could satisfy determinism
>>>>>> for "await" wherever it is used, and satisfy the original motivation:
>>>>>>
>>>>>> ```
>>>>>> await.all {
>>>>>>     for (const item of items) {
>>>>>>         doTheThingAsync(item);
>>>>>>     }
>>>>>> }
>>>>>> ```
>>>>>>
>>>>>> Notice I have omitted `await` inside the loop. Like current
>>>>>> JavaScript, that causes parallel execution, so no change on that front,
>>>>>> from a determinism perspective. So determinism is not hurt by `await.all`.
>>>>>> Rather, it guarantees completion before going further.
>>>>>>
>>>>>> In an earlier example (paraphrase-coded as I forgot the names):
>>>>>>
>>>>>> ```
>>>>>> let x, y;
>>>>>>
>>>>>> await.all {
>>>>>>    x = getXAsync();
>>>>>>    y = getYAsync();
>>>>>> }
>>>>>>
>>>>>> processXAndY(x, y);
>>>>>> ```
>>>>>>
>>>>>> I think the benefit of this syntax appears more stark with the looped
>>>>>> (first) example, as current JavaScript requires building an array in the
>>>>>> loop to subsequently pass to `Promise.all`, which I think is a little more
>>>>>> difficult to conceptualize than the `await.all { ... }` way of doing it.
>>>>>> The 2nd example is arguably better than current JavaScript too,
>>>>>> particularly because the coder doesn't have to be very smart with
>>>>>> destructuring in light of understanding the "Promise.all" return type, etc.
>>>>>> In other words, less cognitive overhead, which I think is a net positive.
>>>>>>
>>>>>> On Fri, 22 Nov 2019 at 13:44, Tom Boutell <tom at apostrophecms.com>
>>>>>> wrote:
>>>>>>
>>>>>>> I am very sympathetic to pitches to allow more common cases for
>>>>>>> promise libraries to be written in an "awaitful" syntax without thinking
>>>>>>> explicitly about promises.
>>>>>>>
>>>>>>> Howeever I think that changing the meaning of the semicolon in a
>>>>>>> particular context has too much potential for confusion. As others have
>>>>>>> said, parallel execution is different, and it should look and feel
>>>>>>> different. The most basic assumption a developer makes (consecutive lines
>>>>>>> of code run consecutively) is difficult to get away from; that's why we
>>>>>>> introduced "await" in the first place, to bring back the ability to write
>>>>>>> deterministic code with consecutive statements. Which sounds like a
>>>>>>> reasonable ask, when it's put that way. (:
>>>>>>>
>>>>>>> I did propose this recently:
>>>>>>>
>>>>>>> for (const item of items concurrency 5) {
>>>>>>>   await  doTheThing(item);
>>>>>>> }
>>>>>>>
>>>>>>> However in this case I'm not talking about consecutive statements,
>>>>>>> I'm only talking about rules for simultaneously (in the sense of async, not
>>>>>>> threads) running more than one instance of the block. So I'm not proposing
>>>>>>> that we change the meaning of the semicolon(s) *within* the block in a way
>>>>>>> that could mean that if you're looking at half the code in the middle you
>>>>>>> would be likely to fundamentally misunderstand its operation.
>>>>>>>
>>>>>>> I think that risk - that you can't tell what a semicolon means
>>>>>>> without reference to the outer context - is what makes your proposal a
>>>>>>> bridge too far for me.
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>>
>>>>>>> THOMAS BOUTELL | CHIEF TECHNOLOGY OFFICER
>>>>>>> APOSTROPHECMS | apostrophecms.com | he/him/his
>>>>>>> _______________________________________________
>>>>>>> es-discuss mailing list
>>>>>>> es-discuss at mozilla.org
>>>>>>> https://mail.mozilla.org/listinfo/es-discuss
>>>>>>>
>>>>>>
>>>>>
>>>>> --
>>>>>
>>>>> THOMAS BOUTELL | CHIEF TECHNOLOGY OFFICER
>>>>> APOSTROPHECMS | apostrophecms.com | he/him/his
>>>>>
>>>>
>>>
>>> --
>>>
>>> THOMAS BOUTELL | CHIEF TECHNOLOGY OFFICER
>>> APOSTROPHECMS | apostrophecms.com | he/him/his
>>>
>>
>>
>> --
>>
>> THOMAS BOUTELL | CHIEF TECHNOLOGY OFFICER
>> APOSTROPHECMS | apostrophecms.com | he/him/his
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20191125/e4e7c2d1/attachment-0001.html>


More information about the es-discuss mailing list