How to Schedule Background Tasks in JavaScript

Craig Buckler
Craig Buckler
Share

If you remember nothing else about JavaScript, never forget this: it blocks. Imagine a magical processing pixie makes your browser work. Everything is handled by that single pixie whether it’s rendering HTML, reacting to a menu command, painting on the screen, handling a mouse click or running a JavaScript function. Like most of us, the pixie can only do one thing at a time. If we throw many tasks at the pixie, they get added to a big to-do list and are processed in order. Everything else stops when the pixie encounters a script tag or has to run a JavaScript function. The code is downloaded (if required) and run immediately before further events or rendering can be handled. This is necessary because your script could do anything: load further code, remove every DOM element, redirect to another URL etc. Even if there were two or more pixies, the others would need to stop work while the first processed your code. That’s blocking. It’s the reason why long-running scripts cause browsers to become unresponsive. You often want JavaScript to run as soon as possible because the code initializes widgets and event handlers. However, there are less important background tasks which don’t directly affect the user experience, e.g.

  • recording analytics data
  • sending data to social networks (or adding 57 ‘share’ buttons)
  • pre-fetching content
  • pre-processing or pre-rendering HTML
These are not time-critical but, in order for the page to remain responsive, they shouldn’t run while the user is scrolling or interacting with the content. One option is to use Web Workers which can run code concurrently in a separate thread. That’s a great option for pre-fetching and processing but you’re not permitted to directly access or update the DOM. You can avoid that in your own scripts but you can’t guarantee it’ll never be required in third-party scripts such as Google Analytics. Another possibility is setTimeout, e.g. setTimeout(doSomething, 1);. The browser will execute the doSomething()
function once other immediately-executing tasks have completed. In effect, it’s put on the bottom of the to-do list. Unfortunately, the function will be called regardless of processing demand.

requestIdleCallback

requestIdleCallback is a new API designed to schedule non-essential background tasks during those moments the browser is taking a breather. It’s reminiscent of requestAnimationFrame which calls a function to update an animation before the next repaint. You can read more about requestAnimationFrame here: Simple Animations Using requestAnimationFrame
We can detect whether requestIdleCallback is supported like so:
if ('requestIdleCallback' in window) {
  // requestIdleCallback supported
  requestIdleCallback(backgroundTask);
}
else {
  // no support - do something else
  setTimeout(backgroundTask1, 1);
  setTimeout(backgroundTask2, 1);
  setTimeout(backgroundTask3, 1);
}
You can also specify an options object parameter with a timeout (in milliseconds), e.g.
requestIdleCallback(backgroundTask, { timeout: 3000; });
This ensures your function is called within the first three seconds, regardless of whether the browser is idle. requestIdleCallback calls your function once only and passes a deadline
object with the following properties:
  • didTimeout — set true if the optional timeout fired
  • timeRemaining() — a function which returns the number of milliseconds remaining to perform a task
timeRemaining() will allocate no more than 50ms for your task to run. It won’t stop tasks exceeding this limit but, preferably, you should call requestIdleCallback again to schedule further processing. Let’s create a simple example which executes several tasks in order. The tasks are stored in an array as function references:
// array of functions to run
var task = [
	background1,
	background2,
	background3
];

if ('requestIdleCallback' in window) {
  // requestIdleCallback supported
  requestIdleCallback(backgroundTask);
}
else {
  // no support - run all tasks soon
  while (task.length) {
  	setTimeout(task.shift(), 1);
  }
}

// requestIdleCallback callback function
function backgroundTask(deadline) {

  // run next task if possible
  while (deadline.timeRemaining() > 0 && task.length > 0) {
  	task.shift()();
  }

  // schedule further tasks if necessary
  if (task.length > 0) {
    requestIdleCallback(backgroundTask);
  }
}

Is There Anything That Shouldn’t Be Done In a requestIdleCallback?

As Paul Lewis notes in his blog post on the subject, the work you do in a requestIdleCallback should be in small chunks. It is not suitable for anything with unpredictable execution times (such as manipulating the DOM, which is better done using a requestAnimationFrame callback). You should also be wary of resolving (or rejecting) Promises, as the callbacks will execute immediately after the idle callback has finished, even if there is no more time remaining.

requestIdleCallback Browser Support

requestIdleCallback is an experimental feature and the spec is still in flux, so don’t be surprised when you encounter API changes. It’s supported in Chrome 47 … which should be available before the end of 2015. Opera should also gain the feature imminently. Microsoft and Mozilla are both considering the API and it sounds promising. There’s no word from Apple as usual. If you fancy giving it a whirl today, your best bet is to use Chrome Canary (a much newer release of Chrome that’s not as well tested, but has the latest shiny stuff). Paul Lewis (mentioned above) created a simple requestIdleCallback shim
. This implements the API as described but it’s not a polyfill which can emulate the browser’s idle-detection behavior. It resorts to using setTimeout like the example above but it’s a good option if you want to use the API without object detection and code forking. While support is limited today, requestIdleCallback could be an interesting facility to help you maximize web page performance. But what do you think? I’d be glad to hear your thoughts in the comments section below.

Frequently Asked Questions (FAQs) about Scheduling Background Tasks in JavaScript

What is the Background Tasks API in JavaScript?

The Background Tasks API in JavaScript is a powerful tool that allows developers to schedule tasks to run in the background, even when the main thread is idle. This API is particularly useful for tasks that require heavy computation or network requests, as it allows these tasks to be performed without blocking the main thread and potentially causing a poor user experience. The Background Tasks API is part of the larger Web APIs provided by modern browsers, and it provides a more efficient and performance-friendly way to handle background tasks compared to traditional methods like setTimeout or setInterval.

How does the Background Tasks API differ from setTimeout and setInterval?

The setTimeout and setInterval functions are traditional methods used in JavaScript for scheduling tasks to run after a certain delay or at regular intervals, respectively. However, these methods have some limitations, particularly when it comes to performance. They run on the main thread, which means they can block other tasks and potentially cause a poor user experience if they take too long to complete. On the other hand, the Background Tasks API runs tasks in the background, separate from the main thread. This means it can handle more intensive tasks without affecting the performance of the main thread.

Can I use the Background Tasks API in all browsers?

The Background Tasks API is a relatively new addition to the Web APIs provided by modern browsers, and as such, it may not be supported in all browsers. It’s always a good idea to check the current level of support for any API you plan to use in your projects. Websites like Can I Use provide up-to-date information on the level of support for various APIs across different browsers.

How can I schedule a task to run in the background using the Background Tasks API?

To schedule a task using the Background Tasks API, you can use the requestIdleCallback method. This method takes a callback function as its first argument, which will be executed when the browser is idle. Here’s a basic example:

window.requestIdleCallback(() => {
// Your background task goes here
});

How can I cancel a scheduled background task?

If you need to cancel a background task that you’ve scheduled with the requestIdleCallback method, you can use the cancelIdleCallback method. This method takes the ID returned by requestIdleCallback as its argument. Here’s an example:

const id = window.requestIdleCallback(() => {
// Your background task goes here
});

// Later, if you need to cancel the task
window.cancelIdleCallback(id);

What is the use of the timeout option in requestIdleCallback?

The timeout option in requestIdleCallback is used to specify a maximum time in milliseconds that the browser should wait before running the callback, even if it’s not idle. This can be useful if your background task needs to be run within a certain timeframe.

How can I handle errors in a background task?

Handling errors in a background task scheduled with the Background Tasks API is similar to handling errors in any other JavaScript code. You can use a try/catch block to catch any errors that occur during the execution of your task. Here’s an example:

window.requestIdleCallback(() => {
try {
// Your background task goes here
} catch (error) {
console.error('An error occurred in the background task', error);
}
});

Can I use the Background Tasks API in Node.js?

The Background Tasks API is a part of the Web APIs provided by modern browsers, and as such, it’s not available in Node.js by default. However, there are other ways to run background tasks in Node.js, such as using worker threads or child processes.

Can I use the Background Tasks API with Promises or async/await?

The Background Tasks API does not return a Promise, so it can’t be used directly with async/await. However, you can wrap the requestIdleCallback method in a Promise if you need to use it in an asynchronous context. Here’s an example:

const runInBackground = (task) => {
return new Promise((resolve) => {
window.requestIdleCallback(() => {
const result = task();
resolve(result);
});
});
};

// You can now use runInBackground with async/await
const result = await runInBackground(() => {
// Your background task goes here
});

What are some use cases for the Background Tasks API?

The Background Tasks API is useful for any tasks that require heavy computation or network requests, as it allows these tasks to be performed without blocking the main thread. Some examples of use cases might include fetching and processing data from an API, performing complex calculations, or updating the UI based on user interactions.