Last active
December 4, 2020 23:10
-
-
Save noseratio/b7de3c7383ae3660904d650cd418c2dd to your computer and use it in GitHub Desktop.
Class representing an abortable Promise using AbortController
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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