Skip to main content

Command Palette

Search for a command to run...

Canceling Asynchronous Operations in JavaScript

Canceling Promises in JavaScript: 3 Modern Approaches

Published
3 min read
Canceling Asynchronous Operations in JavaScript

In JavaScript, Promises don’t natively support cancellation, which can lead to resource leaks or unnecessary network activity. Still, developers can implement “pseudo-cancellation” or real termination using various techniques. Below are three proven methods.

1. Simulating Cancellation with Promise.race()

The idea is simple: race the main Promise against another one that rejects when you call a cancel function. Whichever settles first “wins.”

// createCancelablePromise.js
const createCancelablePromise = (task) => {
  let stopTask;
  const cancelSignal = new Promise((_, reject) => {
    stopTask = () => reject({ name: "CanceledError" });
  });

  const runningTask = Promise.race([task, cancelSignal]);
  return { cancel: stopTask, promise: runningTask };
};

// Example usage
const { cancel, promise } = createCancelablePromise(
  new Promise((resolve) => {
    setTimeout(() => resolve({ result: "Data loaded" }), 3000);
  })
);

promise
  .then((data) => console.log("Resolved:", data))
  .catch((err) => console.warn("Promise ended:", err.name));

setTimeout(() => cancel(), 1500);

Pros: No external libraries, pure JS.
⚠️ Cons: The underlying operation (like setTimeout) still runs.


AbortController is a browser-native solution ideal for canceling fetch requests or streams. Once you call controller.abort(), the request stops immediately.

// abortFetchExample.js
const signalController = new AbortController();

fetch("https://api.example.com/data", { signal: signalController.signal })
  .then((res) => res.json())
  .then((json) => console.log("Received:", json))
  .catch((err) => {
    if (err.name === "AbortError") console.log("Request aborted!");
  });

// Cancel after 2 seconds
setTimeout(() => signalController.abort(), 2000);

Pros: Native API, integrates perfectly with fetch and Axios.
⚠️ Cons: Needs a polyfill for older browsers.

💡 Browser support reference: caniuse.com/abortcontroller


3. Using Bluebird’s Built-In Cancellation API

Bluebird is a feature-rich Promise library supporting cancellation, progress updates, and timeout control.

npm install bluebird
import Bluebird from "bluebird";

// Enable cancellation globally
Bluebird.config({ cancellation: true });

const taskPromise = new Bluebird((resolve, reject, onCancel) => {
  const timer = setTimeout(() => resolve("Success!"), 3000);
  onCancel(() => {
    clearTimeout(timer);
    console.log("Bluebird Promise canceled");
  });
});

taskPromise
  .then((value) => console.log(value))
  .finally(() => {
    if (taskPromise.isCancelled()) console.log("Task was canceled");
  });

setTimeout(() => taskPromise.cancel(), 1000);

Pros: Full cancellation support, advanced control.
⚠️ Cons: Adds an external dependency and increases bundle size.


Comparison Table

MethodAdvantagesLimitationsIdeal Use Case
Promise.race()Simple, native, no dependenciesDoesn’t stop the underlying async operationLightweight tasks
AbortControllerTrue cancellation for fetch/streamsNeeds polyfill for older browsersNetwork requests
BluebirdFull cancellation API + progress & timeoutsExternal dependency, larger bundleComplex async apps

Practical Recommendations

  • Use AbortController for all network-related tasks in modern browsers.

  • Use Promise.race() for simple “soft cancel” needs.

  • Use Bluebird only when advanced async control is required.

Remember: canceling a Promise doesn’t stop the internal side effects like timers or workers—clean up those manually.