Skip to content

Instantly share code, notes, and snippets.

@noseratio
Last active December 4, 2020 23:10
Show Gist options
  • Save noseratio/b7de3c7383ae3660904d650cd418c2dd to your computer and use it in GitHub Desktop.
Save noseratio/b7de3c7383ae3660904d650cd418c2dd to your computer and use it in GitHub Desktop.
Class representing an abortable Promise using AbortController
// by @noseratio
/** Class representing an abort error
* caused by calling {@link AbortController.abort}
* @extends Error
*/
class AbortError extends Error {
/** Creates an error
* @param {string?} message An optional error message
*/
constructor(message) {
super(message?.toString() ?? "Operation aborted");
}
}
/** Class representing an abortable Promise.
* @extends Promise
*/
class AbortablePromise extends Promise {
static get [Symbol.species]() {
return Promise;
}
/** Creates an abortable Promise.
* @param {AbortSignal} externalSignal An external AbortSignal
* to observe for {@link AbortSignal.abort} event.
* @param {{
* resolve: Function,
* reject: Function,
* signal: AbortSignal}} executor Contains callbacks to
* resolve or reject the promise and {@link AbortSignal} to observe.
*/
constructor(externalSignal, executor) {
super((resolve, reject) => withAbort().then(resolve, reject));
async function withAbort() {
const controller = new AbortController();
const onExternalAbort = () => controller.abort();
externalSignal.addEventListener('abort', onExternalAbort);
try {
await new Promise((resolve, reject) => {
const signal = controller.signal;
executor?.({ resolve, reject, signal });
if (externalSignal.aborted) {
throw new AbortError();
}
// this listener is expected to be GC'ed, see @jaffathecake's
// https://jakearchibald.com/2020/events-and-gc/
signal.addEventListener('abort', () => reject(new AbortError()));
});
}
finally {
externalSignal.removeEventListener('abort', onExternalAbort);
}
}
}
}
function delay(ms, outerSignal) {
return new AbortablePromise(outerSignal, ({resolve, signal}) => {
const id = setTimeout(resolve, ms);
signal.addEventListener('abort', () => clearTimeout(id));
});
}
async function main() {
const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
await delay(2000, controller.signal);
}
await main().catch(console.error);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment