9 ms·
This reminds me of something I’d like to see in JavaScript: Loops that accept `await` in their body without pausing the whole loop. This is already possible wi
by fregante 5y ago
This reminds me of something I’d like to see in JavaScript: Loops that accept `await` in their body without pausing the whole loop.
This is already possible with:
await Promise.all(arr.map(async item => {
await item()
})
… but it requires turning the iterable into an array first and it requires calling a function.
Is anyone working on a proposal for this?
async for (const item of iterable)
await item()
- jimmydief 5y agoA little more verbose than your example but https://github.com/tc39/proposal-async-do-expressions https://github.com/tc39/proposal-async-do-expressions would help with this.
- fregante 5y agoThanks! I think it wouldn’t await the whole loop though for (const item of iterable) async do { await item() } This would be equivalent to Promise.all(arr.map(async item => { await item() }) Notice the lack of await before Promise.all
- eurasiantiger 5y agoThere is already this: for await (bar of foo) { } Useful if you have an array of promises you want to handle sequentially.
- fregante 5y ago`for await` is for async iterables, which is different. The loop body will still block the loop. Compare it to the all/map example I wrote.
- eurasiantiger 5y agoTrue, it is sequential and not parallel. I think this syntax with an async generator could be made to do what you seek.
- fregante 5y agoAsync iterables are async generators. They call .next() on the iterable, that returns a promise, the promise resolves with either a new item to loop with, or with `done`, which will stop the loop.
- eurasiantiger 5y agoAfter reading the comment again, isn’t the first example equal to: await Promise.all( arr.map(item => item()) ); The async keyword is just syntactic sugar for immediately returning a Promise for whatever the function eventually returns, so the async-await inside the map is extraneous if item() is already async.
- fregante 5y agoThat’s just an example piece of code. If I used your code there wouldn’t be an `await` in the loop and people wouldn’t understand.
- eurasiantiger 5y agoYeah, but the pattern of awaiting in an async parallel loop in general reduces to what I wrote, does it not? The only case where you would need to await inside the mapper function is when you would further process some asynchronously returned value. This seems like it would add bloat and do unnecessary sync processing inside the async function. It is highly advisable to separate the concerns using function composition, e.g. await Promise.all( arr.map(async item => handleItem( await item() )) ); I’m all for enabling ”await arr.map()” with implied Promise.all(), heh. There actually was a proposal for that under the ”await*” syntax in 2014. One might be tempted to use the thenable interface (don’t!): await Promise.all( arr.map(item => item() .then(handleItem) ) ); Seems like it would work, but it actually only waits for the promises returned from item() and not the .then() part! This introduces a race condition, where the last promised items to get resolved may have their then-callbacks run asynchronously after Promise.all() has already resolved, and other code has executed.
- fregante 5y ago> the pattern of awaiting in an async parallel loop in general reduces to what I wrote, does it not? No. async item => {await item()} // Promise<void> item => item() // Promise<ReturnValue<typeof item>> In general that's how you use maps (by returning something), but here the intent was not to map an array, it's just how to implement an "async for". > The only case where you would need to await inside the mapper function is when you would further process some asynchronously returned value Which is what map is for. Also, once again, that was just how the async loop can be implemented. If anything you're saying that it's "a misuse" of map and thus we need an "async for" > arr.map(async item => handleItem( > await item() > )) That doesn't make any sense, there's still an `await` in the function’s body, so you're still handling it — and there's nothing wrong with that. > await arr.map() That's actually a good QoL improvement, but it doesn't address either of the points I'm talking about (only works on arrays + it requires a function) > but it actually only waits for the promises returned from item() and not the .then() part Wrong. The mapper function returns a Promise that resolves with the return value of handleItem. `await a.then(b).then(c)` awaits the 3 promises in row. To achieve that race condition, you should not return nor await the return value of `then` inside the map method: await Promise.all( arr.map(item => { const p = item(); p.then(handleItem); // "Race condition" return p; }) );