Skip to content
easy-cancelable-promise

Getting started

Install easy-cancelable-promise, create your first CancelablePromise, cancel it, clean up after it and report its progress.

GuideTypeScriptTry an example

Install

terminal
npm install easy-cancelable-promise

The package has no dependencies, works with any framework or none, and ships its own TypeScript types.

Create a CancelablePromise

A CancelablePromise is created like a Promise. The callback receives resolve and reject, and a third parameter with the utilities of the promise. onCancel is the one you will use most: it registers what has to happen when the promise is canceled.

first-promise.ts
import { CancelablePromise } from 'easy-cancelable-promise';

export const wait = (milliseconds: number) =>
  new CancelablePromise<string>((resolve, _reject, { onCancel }) => {
    const timer = setTimeout(() => resolve('Done!'), milliseconds);

    // runs only if the promise is canceled: this is where the work is stopped
    onCancel(() => clearTimeout(timer));
  });

Without onCancel the promise would still be canceled, but the timer would keep running. Canceling a promise tells everyone to stop waiting. onCancel is where the work itself is stopped.

Use it like a Promise

CancelablePromise extends Promise. Everything that accepts a promise accepts it: await, then, catch, finally, Promise.all. On top of that it has a status and a cancel method.

use-it.ts
import { wait } from './first-promise';

export async function example() {
  // it is a Promise: await it, chain it, pass it to anything that takes a promise
  const result = await wait(100);

  console.log(result); // 'Done!'

  const pending = wait(5000);

  console.log(pending.status); // 'pending'

  pending.cancel('No longer needed');

  console.log(pending.status); // 'canceled'

  // a canceled promise rejects with the reason of the cancellation
  await pending.catch((reason) => {
    console.log(reason); // 'No longer needed'
  });

  return result;
}

Cancel a real request

The same pattern works for anything that can be stopped. For fetch, that is an AbortController.

fetch.ts
import { CancelablePromise } from 'easy-cancelable-promise';

export interface User {
  id: number;
  name: string;
}

export const fetchUser = (id: number) =>
  new CancelablePromise<User>((resolve, reject, { onCancel }) => {
    const controller = new AbortController();

    // canceling the promise aborts the request
    onCancel(() => controller.abort());

    fetch(`/api/users/${id}`, { signal: controller.signal })
      .then((response) => response.json() as Promise<User>)
      .then(resolve, reject);
  });

const request = fetchUser(1);

// the user navigated away
request.cancel('User navigated away');

The code that cancels does not need to know there is a request behind the promise. The promise handles its own cleanup.

The callback can also be an async function. An error it throws rejects the promise.

async-callback.ts
import { CancelablePromise } from 'easy-cancelable-promise';

export const load = (url: string) =>
  new CancelablePromise<unknown>(async (resolve, _reject, { onCancel }) => {
    const controller = new AbortController();

    onCancel(() => controller.abort());

    // if fetch or json throws, the promise is rejected with that error
    const response = await fetch(url, { signal: controller.signal });

    resolve(await response.json());
  });

Report progress

The utilities also include reportProgress. Whoever holds the promise listens with onProgress.

progress.ts
import { CancelablePromise } from 'easy-cancelable-promise';

export const upload = (chunks: string[]) =>
  new CancelablePromise<number>((resolve, _reject, { onCancel, reportProgress }) => {
    let sent = 0;
    let timer: ReturnType<typeof setTimeout>;

    onCancel(() => clearTimeout(timer));

    const next = () => {
      sent += 1;

      reportProgress((sent / chunks.length) * 100, { sent });

      if (sent === chunks.length) return resolve(sent);

      timer = setTimeout(next, 10);
    };

    timer = setTimeout(next, 10);
  });

export const example = () =>
  upload(['a', 'b', 'c', 'd']).onProgress((percentage) => {
    console.log(`${percentage}%`); // 25%, 50%, 75%, 100%
  });

Where to go next

Documentation
↑↓ Navigate ↵ Open Esc Closeeasy-cancelable-promise docs
Get started

Promises you can actually control.