Idle Map
Tags: javascript
Published: 2019-06-03 18:43:05

---
title: Idle Map
published: true
tags: javascript
canonical_url: https://blog.parametricstudios.com/posts/idle-map/
---

When you have code to run that's async, you have a few options. You can work with a `Promise`, schedule something to run at a later time with `setTimeout`, or schedule in coordination with the browser's render cycle via `requestAnimationFrame`. Each has its own strength, but now there's a new tool in our async toolkit: `requestIdleCallback`. I want to show off a trick to mix up promise-based tasks against the new `requestIdleCallback` API (we'll just call it `rIC`).

If you want a primer on `rIC`, check out the [Google article](https://developers.google.com/web/updates/2015/08/using-requestidlecallback) from [Paul Lewis](https://developers.google.com/web/resources/contributors/paullewis). You can also get the full API rundown on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback) as well as browser support information from [caniuse](https://caniuse.com/#feat=requestidlecallback).

The idea is to run a list of items through a processing function--essentially like `Array#map`--except we want to ensure we intermittently yield control back to the main thread to remain responsive for user events. We can use `rIC` to schedule each item's processing and check the `IdleDeadline` to see if there is more time to process another item. If not, we can schedule another idle callback. We'll continue this process until every item in the list has been processed.

```js
function idleMap(iterable, processCallback) {
  return new Promise(resolve => {
    let results = [];
    let iterator = iterable[Symbol.iterator]();

    async function processList(idleDeadline) {
      do {
        let iterResult = iterator.next();
        if (iterResult.done) {
          return resolve(results);
        }

        results.push(await processCallback(iterResult.value));
      } while (!idleDeadline.didTimeout);

      requestIdleCallback(processList);
    }

    requestIdleCallback(processList);
  });
}
```

This function, `idleMap` takes your list (`iterable`) and a callback (`processCallback`), and it applies the callback to every item in the list just like `Array#map`. Internally, it uses recursion by defining a closure (`processList`) that it first schedules with an idle callback. Once that function is invoked by the browser, it uses the iterator to pull out items from the list and applies the processing callback on them. After each item, the `do..while` control will evaluate whether or not the idle deadline has expired. If it hasn't, the function is free to process another item. If the deadline has expired, the `do..while` control breaks and schedules another idle callback to continue processing the list. Once the list iterator has been consumed, the promise returned from `idleMap` resolves with the results of each item's processing.

I find that using the [iterator interface](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) works well with the `do..while` control flow by removing the need to managing array indexes. As a major bonus, it also means we can map anything that satisfies the iterable interface. This could be doubly useful since it would allow the use of generator functions, custom objects, and various other non-array types to supply the items to be processed.