Execute Program

JavaScript Concurrency: Canceling Promises

Welcome to the Canceling Promises lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • Some concurrency systems have built-in support for canceling asynchronous code, but JavaScript promises don't have this feature. Once we've attached a then callback to a promise, there's no built-in way to stop it. If we need to cancel an operation, it's up to us to implement the cancelation ourselves.

  • One approach is to have a "canceled" flag that we check. If the flag is set to true by the time the code inside the then runs, we can cancel the work.

  • The example below implements cancelation using this kind of canceled flag. We set the flag to true after 500 ms, but the work doesn't start until 1000 ms. Our then callback checks the flag, sees that canceled is true, and returns early. We don't provide a return value, so it will return undefined.

  • >
    let canceled = false;

    const promise = new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => {
    if (canceled === true) {
    return;
    }
    return 'it ran';
    });

    // Set the canceled flag after 500 ms
    setTimeout(() => { canceled = true; }, 500);

    promise;
    Asynchronous Icon Async Result:
    {fulfilled: undefined}Pass Icon
  • What if we have a lot of slow steps in our promise chain? In that case, we should probably support cancelation at each step. Directly checking for cancelation in every then callback would be cumbersome, but fortunately there's a better way.

  • We can extract the cancelation check into a throwIfCanceled function. That function throws an exception if the canceled flag is true. As usual, any exception thrown inside a then callback leads that promise to reject. The net result is: any cancelation will reject the entire promise chain.

  • >
    let canceled = false;

    function throwIfCanceled() {
    if (canceled) {
    throw new Error('Canceled');
    }
    }

    const promise = new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => 5)
    .then(n => {
    throwIfCanceled();
    return n * 2;
    })
    .then(n => {
    throwIfCanceled();
    return n + 1;
    });

    // Set the canceled flag after 500 ms
    setTimeout(() => { canceled = true; }, 500);

    promise;
    Asynchronous Icon Async Result:
    {rejected: 'Error: Canceled'}Pass Icon
  • The examples in this lesson used a canceled variable declared in the code immediately outside of the promise. However, the flag can live anywhere. For example, slow promise chains in production systems in web backends are more likely to use a cancelation flag value stored in a database, checking the database flag at a few critical points in the process.

  • One final note on cancelation. An earlier lesson mentioned Bluebird, which is an implementation of promises in pure JavaScript. Bluebird promises have built-in support for cancelation, as do some other promise libraries written in JavaScript.

  • If you find yourself working on a project that uses one of those libraries, you may have access to built-in cancelation. However, most new JavaScript code is written using native promises, so we stick to native promises in this course. We recommend that you do the same and use native promises when possible, which means doing cancelation manually.