Last active
June 22, 2024 18:10
-
-
Save samthor/2e11de5976fe673557b0ee14a3cb621a to your computer and use it in GitHub Desktop.
Polyfill/code for the signal argument to addEventListener
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
// This is part of the blog post here: https://whistlr.info/2022/abortcontroller-is-your-friend/ | |
// ...and can be used to detect/polyfill the `signal` argument to addEventListener. | |
// | |
// Note that at writing, 86%+ of active browsers already support it: | |
// https://caniuse.com/mdn-api_eventtarget_addeventlistener_options_parameter_options_signal_parameter | |
// ...but that 92% of browsers support `AbortController` and signal. | |
// | |
// So there's 6% of total browsers which will fail silently when adding the `signal` argument. | |
// Eyeballing it, this is mostly Safari 11-15 and Chrome 66-90. These snippets can help with those targets. | |
// | |
// If there's interest in making this a proper library, I may do that. Hit me up on | |
// Twitter (https://twitter.com/samthor) or leave a comment. | |
/** | |
* Checks whether the current environment supports the `signal` argument to addEventListener. | |
*/ | |
export function hasEventListenerSignalSupport() { | |
if (typeof AbortController === 'undefined') { | |
return false; // doesn't even support AbortController :( | |
} | |
const c = new AbortController(); | |
c.abort(); | |
let signalOnListenerSupport = true; | |
globalThis.addEventListener('_test', () => { | |
// If this fires even though the controller is aborted, there's no support | |
signalOnListenerSupport = false; | |
}, { signal: c.signal }); | |
globalThis.dispatchEvent(new CustomEvent('_test')); | |
return signalOnListenerSupport; | |
} | |
/** | |
* Adds support for the `signal` argument to addEventListener. Throws error if | |
* {@link AbortController} is not supported at all. | |
*/ | |
export function maybeAddEventListenerSignalPolyfill() { | |
if (typeof AbortController === 'undefined') { | |
throw new Error(`can't add, AbortController not supported`); | |
} | |
if (hasEventListenerSignalSupport()) { | |
return; | |
} | |
const orig = EventTarget.prototype.addEventListener; | |
EventTarget.prototype.addEventListener = function(eventName, fn, options) { | |
const self = this; | |
if (options?.signal) { | |
if (!(options.signal instanceof AbortSignal)) { | |
throw new Error(`unexpected type (not AbortSignal) for signal arg`); | |
} | |
if (options.signal.aborted) { | |
return; // do nothing, already aborted | |
} | |
// copy so user can't change us: unlike the fn, the options arg can change, as | |
// long as it has the same values | |
const localOptions = {...options}; | |
options.signal.addEventListener('abort', () => { | |
self.removeEventListener(eventName, fn, localOptions); | |
}); | |
} | |
return orig.call(this, eventName, fn, options); | |
}; | |
} | |
Ok, added it, thanks for your feedback.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@nuxodin great! I'd still copy options though:
You could also:
since that is the important part.