Coming from C# and not having a standard cancellation framework in JavaScript is sad. Until TC39's ECMAScript Cancellation is implemented, I'm using my own CancellablePromise, based on Prex library.
// https://github.com/rbuckton/prex/issues/3
const prex = require('prex');
const { CancellationToken } = require('prex');
/**
* Class representing a cancellable promise.
* @extends Promise
*/
class CancellablePromise extends Promise {
static get [Symbol.species]() {
return Promise;
}
/**
* Create an instance of CancellablePromise promise.
* @param {Function} executor - accepts an object with
* the promise resolving callbacks and the cancellation token:
* { resolve, reject, cancel, token }
* @param {CancellationToken} token - a cancellation token.
*/
constructor(executor, token) {
const withCancellation = async () => {
const linkedSource = new prex.CancellationTokenSource([token]);
try {
const linkedToken = linkedSource.token;
const deferred = new prex.Deferred();
linkedToken.register(() => deferred.reject(new prex.CancelError()));
executor({
resolve: value => deferred.resolve(value),
reject: error => deferred.reject(error),
cancel: () => linkedSource.cancel(),
token: linkedToken,
});
await deferred.promise;
}
finally {
// this will also free all linkedToken registrations
linkedSource.close();
}
};
super((resolve, reject) => withCancellation().then(resolve, reject));
}
}
//
// An example of using CancellablePromise
//
// async delay with cancellation
function delayWithCancellation(timeoutMs, token) {
console.log(`delayWithCancellation: ${timeoutMs}`);
return new CancellablePromise(d => {
const id = setTimeout(d.resolve, timeoutMs);
d.token.register(() => clearTimeout(id));
}, token);
}
// main
async function main() {
const tokenSource = new prex.CancellationTokenSource();
const token = tokenSource.token;
setTimeout(() => tokenSource.cancel(), 2000); // cancel after 2000ms
await delayWithCancellation(1000);
console.log("successfully delayed."); // we should reach here
await delayWithCancellation(3000, token);
console.log("successfully delayed."); // we should not reach here
}
main().catch(error => console.log(`Error caught, ${error}`));