Progress reporting
Report how far the work is from inside a promise, listen from outside, follow it through a chain and stop listening.
Report from inside
A native promise has one moment to say something: the end. A CancelablePromise can report while it works,
through reportProgress.
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.
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.
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.
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.
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
- defer
- Progress through the promise: the live example of this page.