Skip to content
easy-cancelable-promise

Progress reporting

Report how far the work is from inside a promise, listen from outside, follow it through a chain and stop listening.

GuideTypeScriptTry an example

Report from inside

A native promise has one moment to say something: the end. A CancelablePromise can report while it works, through reportProgress.

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

export const processItems = (items: string[]) =>
  new CancelablePromise<string[]>((resolve, _reject, { onCancel, reportProgress }) => {
    const done: string[] = [];
    let timer: ReturnType<typeof setTimeout>;

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

    const next = () => {
      const item = items[done.length];

      done.push(item.toUpperCase());

      // a percentage, and anything else the listeners may want to know
      reportProgress((done.length / items.length) * 100, { item });

      if (done.length === items.length) return resolve(done);

      timer = setTimeout(next);
    };

    timer = setTimeout(next);
  });

reportProgress takes a number and, optionally, any metadata. The number is yours to define. A percentage from 0 to 100 is the convention used by this library and by groupAsCancelablePromise.

Listen from outside

onProgress registers a listener and returns the promise, so it can be chained and awaited in one expression.

listen.ts
import { processItems } from './report';

export async function listen() {
  const updates: string[] = [];

  const result = await processItems(['a', 'b', 'c', 'd'])
    // onProgress returns the promise, so listeners can be chained and awaited
    .onProgress((percentage) => updates.push(`${percentage}%`))
    .onProgress((_percentage, metadata) => updates.push((metadata as { item: string }).item));

  return { result, updates };
  // result: ['A', 'B', 'C', 'D']
  // updates: ['25%', 'a', '50%', 'b', '75%', 'c', '100%', 'd']
}

The metadata is typed unknown. Narrow it before use.

Through a chain

The progress of a promise reaches the promises created from it with then, catch and finally. You can listen on the last promise of a chain.

chain.ts
import { processItems } from './report';

export async function chain() {
  const updates: number[] = [];

  // the progress of a promise reaches every promise chained from it
  const summary = processItems(['a', 'b']).then((items) => items.join(', '));

  summary.onProgress((percentage) => updates.push(percentage));

  return { text: await summary, updates }; // { text: 'A, B', updates: [50, 100] }
}

Stop listening

Pass a signal to onProgress and abort it. Inside the callback, the onProgress utility returns the function that removes the listener.

unsubscribe.ts
import { CancelableAbortController } from 'easy-cancelable-promise';
import { processItems } from './report';

export async function unsubscribe() {
  const updates: number[] = [];
  const controller = new CancelableAbortController();

  const task = processItems(['a', 'b', 'c', 'd']).onProgress(
    (percentage) => {
      updates.push(percentage);

      // stop listening after the first half
      if (percentage >= 50) controller.abort();
    },
    { signal: controller.signal },
  );

  await task;

  return updates; // [25, 50]
}

Report from outside

reportProgress is also a method of the promise, and of the object defer returns. Use it when the code that knows the progress is not the callback.

report-outside.ts
import { defer } from 'easy-cancelable-promise';

export function reportFromOutside() {
  const updates: number[] = [];
  const deferred = defer<string>();

  deferred.promise.onProgress((percentage) => updates.push(percentage));

  // reportProgress is also a method of the promise and of the deferred object
  deferred.promise.reportProgress(30);
  deferred.reportProgress(60);
  deferred.resolve('Done!');

  return updates; // [30, 60]
}

After a promise settles its listeners are removed, so later reports reach nobody.

Next

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

Promises you can actually control.