Real Cancellable Promise
`real-cancellable-promise` is a robust and lightweight library offering a cancellable Promise implementation for JavaScript and TypeScript. Unlike many other approaches that merely prevent callbacks from executing, this library focuses on propagating cancellation signals to the underlying asynchronous operations, such as network requests made with `fetch`, `axios`, or `jQuery.ajax`, thereby releasing resources and truly aborting tasks. It boasts zero dependencies and a minimal footprint, under 1 kB minified and gzipped. The current stable version is 1.2.3, with ongoing active maintenance reflected in its consistent bug fix releases. Key differentiators include its explicit support for 'real' cancellation and its compatibility with popular ecosystems like React (solving issues like `setState` after unmount and handling variable query parameters) and `react-query`'s cancellation features out-of-the-box. It supports modern browsers (excluding Internet Explorer) and Node.js 14+ (with `AbortController` functionality requiring Node 15+).
Common errors
-
TypeError: cancellablePromise.cancel is not a function
cause This usually happens when you try to call `.cancel()` on a regular `Promise` instance or on a `CancellablePromise` that was not correctly constructed or imported.fixEnsure you are creating an instance of `CancellablePromise` (e.g., `new CancellablePromise(...)`) and that you have correctly imported `CancellablePromise` from the package. Double-check that you are not mistakenly assigning a standard Promise to your `cancellablePromise` variable. -
ReferenceError: Cancellation is not defined
cause The `Cancellation` error class is a named export and must be explicitly imported before use.fixAdd `import { Cancellation } from 'real-cancellable-promise';` (for ESM) or `const { Cancellation } = require('real-cancellable-promise');` (for CJS) at the top of your file. -
Unhandled Promise Rejection: Cancellation: Request for post 1 was cancelled.
cause A `CancellablePromise` was cancelled, but the resulting `Cancellation` error was not caught by a `.catch()` handler in the promise chain.fixAdd a `.catch()` block to your promise chain to handle `Cancellation` errors. It's often good practice to specifically check `if (error instanceof Cancellation)` to handle cancellations differently from other errors. -
SyntaxError: Named export 'CancellablePromise' not found. The requested module 'real-cancellable-promise' does not provide an export named 'CancellablePromise'
cause This error typically occurs in a CommonJS environment (or an older Node.js version/bundler) when attempting to use ES module `import { NamedExport } from 'module';` syntax, and the module isn't correctly transpiled or resolved as CJS.fixIf in a CommonJS context, use `const { CancellablePromise } = require('real-cancellable-promise');`. If using a bundler, ensure it's configured to correctly transpile or resolve ESM. Ensure your Node.js version is compatible with the module format you are using (Node.js 14+ is generally supported, but `import` syntax might need specific configuration in older Node.js environments).
Warnings
- gotcha In versions prior to 1.2.3, a `Cancellation` error might not be thrown as expected within a `.then()` chain if the initial promise had already resolved, leading to unhandled promise rejections or unexpected behavior.
- gotcha Version 1.2.1 fixed a memory leak that occurred when `buildCancellablePromise` was used to create very long-running tasks, potentially impacting application stability over time.
- gotcha The package started publishing an ES module alongside a CommonJS module in v1.1.0. While bundlers and Node.js generally pick the correct format, older tools or specific configurations might encounter module resolution issues.
- gotcha The `CancellablePromise` constructor requires the `cancel` function to explicitly cause the wrapped promise to reject with a `Cancellation` object. Simply providing an empty function or a function that doesn't reject will not achieve 'real' cancellation.
- gotcha Prior to version 1.1.1, `CancellablePromise<T>` was not directly assignable to `Promise<T>` in TypeScript, leading to type errors when integrating with APIs expecting standard Promises.
Install
-
npm install real-cancellable-promise -
yarn add real-cancellable-promise -
pnpm add real-cancellable-promise
Imports
- CancellablePromise
const CancellablePromise = require('real-cancellable-promise').CancellablePromise;import { CancellablePromise } from 'real-cancellable-promise'; - buildCancellablePromise
import buildCancellablePromise from 'real-cancellable-promise';
import { buildCancellablePromise } from 'real-cancellable-promise'; - Cancellation
const { Cancellation } = require('real-cancellable-promise');import { Cancellation } from 'real-cancellable-promise';
Quickstart
import { CancellablePromise, Cancellation } from 'real-cancellable-promise';
interface Post { id: number; title: string; body: string; }
// Simulate an API call with AbortController for real cancellation
function fetchPostWithCancellation(postId: number): CancellablePromise<Post> {
const abortController = new AbortController();
const signal = abortController.signal;
const promise = fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`, { signal })
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
return new CancellablePromise(
promise,
() => {
abortController.abort(new Cancellation(`Request for post ${postId} was cancelled.`));
}
);
}
async function runExample() {
console.log('Fetching post 1...');
const cancellableFetch = fetchPostWithCancellation(1);
try {
// Start the fetch but cancel it after a short delay
const timeoutId = setTimeout(() => {
console.log('Attempting to cancel fetch for post 1...');
cancellableFetch.cancel();
}, 50);
const post = await cancellableFetch;
clearTimeout(timeoutId);
console.log('Fetched post:', post.title);
} catch (error) {
if (error instanceof Cancellation) {
console.log('Fetch for post 1 was successfully cancelled:', error.message);
} else {
console.error('Fetch for post 1 failed:', error);
}
}
// Demonstrate a successful fetch
console.log('\nFetching post 2...');
const successfulFetch = fetchPostWithCancellation(2);
try {
const post = await successfulFetch;
console.log('Successfully fetched post 2:', post.title);
} catch (error) {
console.error('Fetch for post 2 failed:', error);
}
}
runExample();