Last active
June 11, 2024 12:13
-
-
Save CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c to your computer and use it in GitHub Desktop.
Memory-safe Polyfill Implementation for `AbortSignal.any`
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
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 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.