Last active
March 24, 2025 13:57
-
-
Save CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c to your computer and use it in GitHub Desktop.
Memory-safe Polyfill Implementation for `AbortSignal.any`
This file contains hidden or 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
const registry = new FinalizationRegistry((callback) => void callback()); | |
export function polyfillAbortSignalAny() { | |
/** @param {AbortSignal[]} signals */ | |
return (signals) => { | |
// if (AbortSignal.any) { | |
// return AbortSignal.any(signals); | |
// } | |
const controller = new AbortController(); | |
for (const signal of signals) { | |
if (signal.aborted) { | |
controller.abort(signal.reason); | |
return controller.signal; | |
} | |
} | |
const controllerRef = new WeakRef(controller); | |
/** @type {[WeakRef<AbortSignal>, (() => void)][]} */ | |
const eventListenerPairs = []; | |
let followingCount = signals.length; | |
signals.forEach((signal) => { | |
const signalRef = new WeakRef(signal); | |
function abort() { | |
controllerRef.deref()?.abort(signalRef.deref()?.reason); | |
} | |
signal.addEventListener("abort", abort); | |
eventListenerPairs.push([signalRef, abort]); | |
registry.register(signal, () => !--followingCount && clear(), signal); | |
}); | |
function clear() { | |
eventListenerPairs.forEach(([signalRef, abort]) => { | |
const signal = signalRef.deref(); | |
if (signal) { | |
signal.removeEventListener("abort", abort); | |
registry.unregister(signal); | |
} | |
const controller = controllerRef.deref(); | |
if (controller) { | |
registry.unregister(controller.signal); | |
delete controller.signal.__controller; | |
} | |
console.log("clear", ++count); | |
}); | |
} | |
const { signal } = controller; | |
registry.register(signal, clear, signal); | |
signal.addEventListener("abort", clear); | |
registry.register(controller, () => console.log("controller", count)); | |
signal.__controller = controller; | |
return signal; | |
}; | |
} | |
export let count = 0; // for test only |
This file contains hidden or 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
// node --expose-gc | |
({ polyfillAbortSignalAny } = await import("./polyfill.js")); | |
any = polyfillAbortSignalAny(); | |
a = Array.from({ length: 2 }).map(() => { const c = new AbortController(); return [c, any([c.signal])]; }) | |
[x, y] = a[0] | |
x.abort() | |
// a = a.map(([x, y]) => x) // to remove the references for followers | |
// a = a.map(([x, y]) => y) // to remove the references for origins | |
gc() | |
// look at the output |
Thank you for your advice! I added a check for each signal and early return if any of them is aborted. I think this would solve this case.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for sharing this polyfill!
I noticed that the check for
signal.aborted
is missing in the implementation. Including this check would ensure that already aborted signals are handled correctly.Thanks again!