# Async \`for\` loops (allow \`await\` without blocking the loop)

**URL:** <https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765>\
**Category:** 💡 Ideas\
**Created:** [July 28, 2023, 6:06pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765 "2023-07-28T18:06:26Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![fregante](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/fregante/32/1820_2.png) [@fregante](https://es.discourse.group/u/fregante)\
**Post date:** [July 28, 2023, 6:06pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/1 "2023-07-28T18:06:26Z")

</div>

Example:

```js
for (const user of userList) {
  await pingUser(user);
  console.log(user.name, 'pinged')
}
console.log('done')

```

Expectations:

- call `pingUser` on each item of the Set
- log user name after each ping
- log `done` after they've all been pinged
- call `pingUser` "in parallel" (expectation not met)

The closest piece of code I've been using for this pattern is:

```js
await Promise.all([...userList].map(async user => {
  await pingUser(user);
  console.log(user.name, 'pinged');
}));
console.log('done')

```

This is quite a mouthful and it awkwardly uses .map+.all to await promises.

Possible solution:

```js
for (const user of userList) async {
  await pingUser(user);
  console.log(user.name, 'pinged')
}
console.log('done')

```

## Notes

1. `async` here doesn't just define an "async block" but it would be part of the `for` loop itself; the loop would still need to be awaited as a whole.
2. this is not equivalent to `for () { /* async IIFE */ }` because that would not wait for the IIFEs to complete.

## Prior art

ESLint offers a [`no-await-in-loop` rule](https://eslint.org/docs/latest/rules/no-await-in-loop) which could be elegantly fixed with this `async` keyword **in some cases**

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [July 28, 2023, 8:44pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/2 "2023-07-28T20:44:25Z")

</div>

I wonder how much the [Async Iterators](https://github.com/tc39/proposal-async-iterator-helpers) proposal could help here.

```javascript
await userList
  .values()
  .map(async (user) => {
    await pungUser(user);
    console.log(user.name, 'pinged');
  })
  .toAsync()
  .toArray();

```

Might be nice to have some other way to 'end' the chain without `toArray`, as that will need to buffer all the `undefined` results here. Maybe `toPromise()`.

Or maybe if there was a concurrent version of `forEach`, or equivalent, as discussed [here](https://github.com/tc39/proposal-async-iterator-helpers/issues/6).

```javascript
await userList
  .values()
  .toAsync()
  .forEach(async (user) => {
    await pungUser(user);
    console.log(user.name, 'pinged');
  });

```

A benefit of using iterator helpers for this, vs for-each syntax, might be being able to control the concurrency.

CC @bakkot , @michael

---

<div class="post-metadata">

**Author:** ![bakkot](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bakkot/32/22_2.png) [@bakkot](https://es.discourse.group/u/bakkot)\
**Post date:** [July 28, 2023, 9:33pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/3 "2023-07-28T21:33:28Z")

</div>

Iterator helpers get you almost all the way there, but it's awkward:

```js
await userList
  .values()
  .toAsync()
  .map(async (user) => {
    await pungUser(user);
    console.log(user.name, 'pinged');
  })
  .bufferAhead(3); // or whatever degree of concurrency you want
  .forEach(() => {}); // drain

```

The "almost" qualifier is because this doesn't let you early exit, whereas `for` does (though it's a bit unclear how that would translate to concurrent a concurrent `for`).

A concurrent `forEach` would simplify that a bit. But yeah, syntax would be better still. I've been thinking some about how to do that, though only very speculatively. Do note that you almost certainly want to specify the degree of concurrency explicitly, at least when using a pattern like this.

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [July 30, 2023, 3:52pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/4 "2023-07-30T15:52:34Z")

</div>

This is something I've wanted as well. My ideal syntax would probably be along the lines of this:

```javascript
for parallel (const user of userList) {
  await pingUser(user);
  console.log(user.name, 'pinged')
}
console.log('done')

```

I like the word "parallel" because 1. We already have a "for await", having another for loop style that uses the word "await" or "async" is just going to feel messy, and 2. what we're specifically trying to do is execute each iteration in parallel instead of in series. Though, on the other hand, it would be kind-of nice if the `await` keyword was in there somewhere as well, because conceptually we would be awaiting this loop too. But `await for parallel (...) { ... }` feels a little more messy.

Perhaps an API solution would work better. Not exactly sure what this proposed `.bufferAhead()` does, but I assume it makes it so only that number of parallel tasks are running at a time? New ones don't start until the old ones are done? (I don't see `.bufferAhead()` in the current [iterators helpers proposal](https://github.com/tc39/proposal-iterator-helpers)). If that is what it does, then that would sound extremely nice as well, and would make an API version sound nicer than a syntax version (sure you could add the ability to parametarize the syntax with something like that, but that will make the syntax feel much more heavy-weight).

* * *

To put some of my own experience into this issue, I've attempted to explain the `Promise.all()` + `array.map()` pattern to my teammates, but they seem reluctant to use it, partly because it's fairly clunky, but also because if you don't have a strong grasp on how promises and what-not work, it can be a little difficult to understand what that pattern is doing. And I can sympathise with these issues. So, they prefer creating an empty list, pushing promises into it in a for loop, then `await Promise.all()` that hand-built list.

With JavaScript being so built on asynchronous programming, it would be nice if there was just a native syntax or API or something that could be used to accomplish this very common task. Something that communicates clearly your intentions, instead of requiring us to either tediously hand-build a promise list, or to use a somewhat-tricky pattern that can be difficult to understand if you don't have a strong grasp of promises yet.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [July 31, 2023, 3:27am UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/5 "2023-07-31T03:27:24Z")

</div>

How would `break` work within the loop?

```javascript
for parallel (let i = 0; i < 3; i++) {
  console.log(i);
  if (i === 0) {
    try {
       await Promise.resolve(1);
       console.log("resolved");
    } finally {
       console.log("finally");
    }
  }
  else if (i === 1) {
     break;
  }
}

```

- A) `SyntaxError` no `break`
- B) logs: `0, 1, finally`
- C) logs: `0, 1, resolved, finally`
- D) other?

Similarly, what happens if the `break` were `throw new Error`?

---

<div class="post-metadata">

**Author:** ![fregante](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/fregante/32/1820_2.png) [@fregante](https://es.discourse.group/u/fregante)\
**Post date:** [July 31, 2023, 6:36am UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/6 "2023-07-31T06:36:10Z")

</div>

My first reaction would be that `break` stops the single iteration, exactly like `return` would in the .map() version. It might make sense to have it be a SyntaxError though.

As for errors, it would behave like the .map version:

- other iterations continue
- the `for` block immediately throws an error

```js
try {
  for parallel (const a of list) {
    if (await a === 0) throw new Error('Zero found')
  }
} catch (error) {
  // Zero found
}

```

A `return` could work like a promise race:

- other iterations continue
- the function containing the `for parallel` returns the value.

---

<div class="post-metadata">

**Author:** ![aclaymore](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/aclaymore/32/501_2.png) [@aclaymore](https://es.discourse.group/u/aclaymore)\
**Post date:** [July 31, 2023, 8:02am UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/7 "2023-07-31T08:02:42Z")

</div>

> [@fregante](#):
>
> My first reaction would be that `break` stops the single iteration, exactly like `return` would in the .map() version. It might make sense to have it be a SyntaxError though.

In that situation it sounds more like `continue` so yeah maybe better to SyntaxError, rather than have two keywords with the same semantics.

> [@fregante](#):
>
> As for errors, it would behave like the .map version:
> 
> - other iterations continue

In the `.map` version if the throw happens before the first `await` then it would stop the iteration. For the loop would it always let the iteration continue?

---

<div class="post-metadata">

**Author:** ![fregante](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/fregante/32/1820_2.png) [@fregante](https://es.discourse.group/u/fregante)\
**Post date:** [July 31, 2023, 8:42am UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/8 "2023-07-31T08:42:58Z")

</div>

You're right, it might make sense to have synchronous `return`s/`throw`s block the loop:

```js
for parallel (const item of [1,2,3]) {
  console.log(item);
  return item; // or throw
}
// log: 1

```

but if they come after an `await` then it would be too late to stop the loop:

```js
for parallel (const item of [1,2,3]) {
  console.log(item);
  await 1;
  return item; // or throw
}
// log: 1
// log: 2
// log: 3

```

* * *

Also I think `for parallel` would not work because async iterators should still be possible:

```js
for await (const request of httpServer) async {
  await handleRequest(request);
  console.log('request handled', request.id)
}

```

---

<div class="post-metadata">

**Author:** ![theScottyJam](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/thescottyjam/32/616_2.png) [@theScottyJam](https://es.discourse.group/u/theScottyJam)\
**Post date:** [August 1, 2023, 2:04pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/9 "2023-08-01T14:04:36Z")

</div>

> [@aclaymore](#):
>
> How would `break` work within the loop?

I agree that `break` should be a syntax error. It doesn't make sense to give one loop iteration the power to try and break the loop when other iterations have already started.

With `throw new Error()`, we can think of `for parallel` as implicitly having an `await` on it, so the first error that happens during iteration can be thrown at the point where the loop was defined, while the rest of the errors will be ignored, similar to how `Promise.all()` behaves.

Though... what if the user would prefer the semantics of `allSettled()`? Or one of the other promise functions?

And then comes the question of `return`. Really, that ought to throw a syntax error if used in a "for parallel" loop as well, since it doesn't really make sense to let different iterations try and return a value.

Then there's the issue that if we really want to match the power of the `Promise.all()` + `array.map()`, we need to allow the for loop to be used in an expression position, and allow the loop bodyies to specify a completion value, i.e. something like this:

```javascript
const listOfResults = for parallel (const x of arrayOfItems) {
  const y = await calcResult(x);
  y;
}

```

But, I'm sure having loop syntax that can be used in an expression position sometimes and can't other times, depending on if the "parallel" word is present would be a strong no-go.

* * *

Basically, all of this is helping me realize more that, while this type of "parallel execution" shares some semantics with for loops, it's also very different, and it would probably cause more confusion than it would solve if we tries to pretend they were similar. If we do want a native syntax, we probably need to drop the "for" word entirely.

```javascript
const listOfResults = await parallel (const x of arrayOfItems) {
  const y = await calcResult(x);
  y;
}

```

Either that or use a new API instead of syntax.

(I'm still not certain how best to handle the issue of deciding which promise-aggregation semantics you want, either with syntax, or with an API - Promise.all() is the most common, but other Promise.\*() methods are often needed as well).

---

<div class="post-metadata">

**Author:** ![fregante](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/fregante/32/1820_2.png) [@fregante](https://es.discourse.group/u/fregante)\
**Post date:** [August 1, 2023, 3:46pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/10 "2023-08-01T15:46:34Z")

</div>

> what if the user would prefer the semantics of allSettled()? Or one of the other promise functions?

They're all possible, but such `for parallel` pattern is probably not the best solution for that. The point of this proposal would be to abstract promises away, rather than provide exact equivalences.

### allSettled

```js
for (const a of abc) async {
  try {
    await a;
  } catch {}
}

```

### race

```js
for (const a of abc) async {
  await a;
  if (logic) {
    return;
  } else {
    throw new Error()
  }
}

```

### any

```js
for (const a of abc) async {
  try {
    await a;
  } catch {
    continue;
  }
  return;
}

throw new Error()

```

> it doesn't really make sense to let different iterations try and return a value

I disagree. If `Promise.race(x.map(async () => {return 1}))` makes sense, then this does too. Regular `for` loops can also exit the loop early, even if previous iterations started async operations. If that's important you'd have to set up your own AbortSignal manually anyway.

> if we really want to match the power of the `Promise.all()` + `array.map()`

If you want to map values, use map. I only used "map" in my example because there's no alternative. This idea does not intend to create "`for` expressions", which is a whole other can of worms

---

<div class="post-metadata">

**Author:** ![fregante](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/fregante/32/1820_2.png) [@fregante](https://es.discourse.group/u/fregante)\
**Post date:** [August 1, 2023, 6:17pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/11 "2023-08-01T18:17:01Z")

</div>

The closest alternative/proposal to this would be [`await.ops`](https://github.com/tc39/proposal-await.ops):

```js
await.all [...userList].map(async user => {
  await pingUser(user);
  console.log(user.name, 'pinged');
});
console.log('done')

```

This is reasonable, but:

- the `.map` callback still doesn't explicitly `return`, so it's not a standard/expected usage (there's a native [eslint rule](https://eslint.org/docs/latest/rules/array-callback-return) to enforce this)
- it lacks the ability to `return` early (early `throw` is supported)

---

<div class="post-metadata">

**Author:** ![Josh-Cena](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/josh-cena/32/1408_2.png) [@Josh-Cena](https://es.discourse.group/u/Josh-Cena)\
**Post date:** [August 2, 2023, 3:50pm UTC](https://es.discourse.group/t/async-for-loops-allow-await-without-blocking-the-loop/1765/12 "2023-08-02T15:50:31Z")

</div>

> [@fregante](#):
>
> the `.map` callback still doesn't explicitly `return`, so it's not a standard/expected usage (there's a native [eslint rule](https://eslint.org/docs/latest/rules/array-callback-return) to enforce this)

The `Promise.all(Promise<void>[])` pattern is pretty idiomatic. The ESLint rule also doesn't check async functions: [https://github.com/eslint/eslint/blob/47a08597966651975126dd6726939cd34f13b80e/lib/rules/array-callback-return.js#L241-L244](https://github.com/eslint/eslint/blob/47a08597966651975126dd6726939cd34f13b80e/lib/rules/array-callback-return.js#L241-L244)
