race-cancellation
Version:
Utilities for using Promise.race([task, cancellation]) for async/await code.
62 lines • 2.22 kB
JavaScript
// we are casting the sentinel value to a unique symbol
// to get === union narrowing
const sentinelValue = {
toString: () => "[cancel sentinel]",
};
/**
* Create a {@link RaceCancelFn} function.
*
* @param isCancelled - a function that returns whether or not we are currently cancelled
* @param executor - a callback to resolve the cancellation
* @param cancelReason - an optional reason for the cancellation
* @public
*/
export default function newRaceCancel(isCancelled, executor, cancelReason) {
// the async cancel executor should only run 0 or 1 times
const memoizedAsyncCancel = memoizeAsyncCancel(isCancelled, executor);
return async (asyncFnOrPromise) => throwIfCancelled(await raceCancel(asyncFnOrPromise, isCancelled, memoizedAsyncCancel), cancelReason);
}
function raceCancel(asyncFnOrPromise, isCancelled, memoizedAsyncCancel) {
return typeof asyncFnOrPromise === "function"
? isCancelled()
? memoizedAsyncCancel()
: Promise.race([asyncFnOrPromise(), memoizedAsyncCancel()])
: Promise.race([asyncFnOrPromise, memoizedAsyncCancel()]);
}
function throwIfCancelled(result, cancelReason) {
if (result === sentinelValue) {
throw intoCancelError(cancelReason);
}
return result;
}
function memoizeAsyncCancel(isCancelled, executor) {
let promise;
return () => {
if (promise === undefined) {
promise = isCancelled()
? Promise.resolve(sentinelValue)
: waitForCancel(executor);
}
return promise;
};
}
async function waitForCancel(cancelExecutor) {
await new Promise(cancelExecutor);
return sentinelValue;
}
function intoCancelError(cancelReason) {
if (typeof cancelReason === "function") {
cancelReason = cancelReason();
}
if (cancelReason === undefined || typeof cancelReason === "string") {
return newCancelError(cancelReason);
}
return cancelReason;
}
function newCancelError(message = "The operation was cancelled") {
const cancelError = new Error(message);
cancelError.name = "CancelError";
cancelError.isCancelled = true;
return cancelError;
}
//# sourceMappingURL=newRaceCancel.js.map