Skip to content

Instantly share code, notes, and snippets.

@CNSeniorious000
Last active June 11, 2024 12:13
Show Gist options
  • Save CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c to your computer and use it in GitHub Desktop.
Save CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c to your computer and use it in GitHub Desktop.
Memory-safe Polyfill Implementation for `AbortSignal.any`
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
// 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
@CNSeniorious000
Copy link
Author

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