A pattern for asynchronous loops in JavaScript

For loop-heavy JavaScript code, I've found it can be helpful to use both a combination of async & traditional loops.
1 min read

First thing’s first

Using promises/async tasks tends to be slow when there’s a large amount of small tasks to be done. Proof of this won’t be provided here, since there’s plenty of great posts on this already. Using a traditional for/while loop can be a lot quicker, at the expense that it blocks the event loop until its done.

If you have something highly iterative, and don’t want to lock up the event loop - executing work in batches may work best.

We can batch the workload into chunks of at most size ITERATIONS_PER_BATCH and execute them asynchronously using setTimeout(). This way, we can still multitask other things while the algorithm is running while retaining most of the performance of a native loop. By wrapping it in a Promise, you can await it or use like you normally would.

Let’s use both!

With this, there’s a couple things to note. You can add variables outside of innerTask for keeping state between batches. One final note which is left as an exercise to the reader, is to add an exit condition depending on what you are doing. Without it, this will loop infinitely.

// you can play with this value, greatly depends on how fast/slow your code is.
// this is the number of iterations will run in each batch, at most.
const ITERATIONS_PER_BATCH = 100

async function doSomethingBig () {
  return new Promise((resolve, reject) => {
    const innerTask = () => {
      for (let i = 0; i < ITERATIONS_PER_BATCH; i++) {
        // DO YOUR STUFF HERE
        // declare variables outside of innerTask and use for keeping state between tasks
      }

      // TODO: make sure you add an exit condition,
      // this setTimeout() queues the next batch to be processed.
      setTimeout(innerTask, 0) // schedule subsequent tasks
    }

    setTimeout(innerTask, 0) // run first task
  })
}

Subscribe to my Newsletter

Like this post? Subscribe to get notified for future posts like this.

Change Log

  • 9/7/2024 - Initial Revision

Found a typo or technical problem? file an issue!