javascript: awaiting asynchronous operations on lists (arrays)

2022-01-18

 | 

~2 min read

 | 

366 words

I’ve written previously about converting syncrhonous code into its asynchronous equivalent, but there’s an edge case that’s worth talking about separately: dealing with collections.

Imagine you have a set of things. They’re collected in an interable, like an array. You now want to operate on that collection, but the operation takes time (and is asynchronous).

How do you make sure that all of those operations have completed? What about just one of them?

Consider a contrived example:

const collection = [
  { operation: "add", inputs: [2, 4] },
  { operation: "multiply", inputs: [2, 4] },
  { operation: "exponent", inputs: [2, 4] },
]

We want to perform the calculations on this collection on the server, and each one is done at a separate endpoint, so we can’t just send them all together. (Remember, this is contrived.)

collection.map((item) =>
  fetch(`/${item.operation}`, {
    method: "GET",
    body: item.inputs,
  }),
)

Okay, so now we’ve sent the data off… but we haven’t awaited anything, so we can’t do anything with the results, which is a bit of a bummer. Let’s fix that.

parallelAsync.js
const res = Promise.all(
  collection.map(async (item) => {
    return await fetch(`/${item.operation}`, {
      method: "GET",
      body: item.inputs,
    })
  }),
)

Now, at this point, res is still a promise, but once it settles, we know that all of the internal items have settled.

If you only need one of the promises to resolve before proceeding, you can use Promise.race.

Promise.all is (often) preferable to a for loop where we await each promise individually because in the Promise.all scenario, we get the benefit of multiple promises working simultaneously, whereas in the latter we block the start of the later promises until the earlier promises settle.

That said, there are situations when you might want to block the later promises. Imagine a sensitive transaction where you don’t want the later operations to start if the earlier ones failed.

blockingAsync.js
const getResults = async () => {
  const res = []
  for (let item of collection) {
    res.push(
      await fetch(`/${item.operation}`, {
        method: "GET",
        body: item.inputs,
      }),
    )
  }
  return res
}

Thanks to Afif Sohaili for his write up! It was quite helpful in getting started!


Related Posts
  • Node: Async filtering


  • Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!