gc-hook: Simplified FinalizationRegistry
gc-hook is a utility library that simplifies the use of JavaScript's `FinalizationRegistry` for managing object lifecycle and reacting to garbage collection. The current stable version is 0.4.1, with the last publish being a year ago, suggesting a mature but not rapidly evolving codebase. It differentiates itself by addressing common pitfalls associated with `FinalizationRegistry`, such as preventing accidental leaks of the registered reference, allowing flexible proxy overrides, and providing mechanisms for explicit deregistration via held references or tokens. Its primary goal is to abstract away the complex specifics of `FinalizationRegistry`, enabling developers to focus on application logic rather than the intricacies of memory management. It works in both CommonJS and ES module environments, offering broad compatibility.
Common errors
-
My cleanup callback function never runs, or runs much later than expected.
cause Garbage collection is non-deterministic and controlled by the JavaScript engine. It is not guaranteed to run at any specific time or even at all before a process exits. This is a fundamental characteristic of `FinalizationRegistry`.fixUnderstand and communicate that `FinalizationRegistry` is for 'best-effort' cleanup, not guaranteed resource release. For critical resources, implement explicit `close()` or `dispose()` methods. To observe GC activity more reliably in development, try running with `--expose-gc` flag in Node.js and manually calling `global.gc()`. -
My object is still in memory even after I set all its references to `null`.
cause There might still be a strong reference to the object preventing it from being garbage collected. This could be an accidental closure, a global reference, or an issue within the `onGarbageCollected` callback itself if it retains a strong reference to the `heldValue` or `target`.fixThoroughly inspect all code paths for strong references. Use a memory profiler (e.g., Chrome DevTools heap snapshot) to identify what is still holding onto the object. Ensure the `onGarbageCollected` callback does not create new strong references to the object it's meant to clean up. If using `gc-hook/track`, remember it uses `console.debug`, which might have its own retention behavior in some environments. -
I'm trying to access a property on the object returned by `create` but it behaves unexpectedly or causes errors related to Proxies.
cause By default, `gc-hook`'s `create` function returns a `Proxy` around the target object to prevent strong references. Direct interaction with this proxy might behave differently from the raw object, especially with operations that assume direct object identity or prototype chain traversal.fixIf you need to interact with the raw object or a specific wrapper, consider using the `return` option in `create`. For example, `create(ref, onGC, { return: ref })` would return the `ref` itself, though this negates the strong reference protection `gc-hook` provides and should only be done if you are sure about managing references externally.
Warnings
- gotcha Garbage collection (GC) and thus `FinalizationRegistry` callbacks are inherently non-deterministic. Cleanup functions are not guaranteed to execute immediately, or even at all, if the process terminates before GC runs. Do not rely on them for essential program logic or timely resource release.
- gotcha By default, `create` returns a `Proxy` around the registered object. This proxy is what prevents the original object from being prematurely garbage collected. Developers should be aware of proxy behavior, especially when relying on strict object identity checks or direct property enumeration, which might behave differently than with plain objects.
- gotcha A common footgun with `FinalizationRegistry` is inadvertently creating a strong reference to the target object within the cleanup callback itself. While `gc-hook` is designed to mitigate this, developers must ensure their `onGarbageCollected` function does not capture or re-establish a strong reference to the object it is intended to finalize, which would prevent its garbage collection.
Install
-
npm install gc-hook -
yarn add gc-hook -
pnpm add gc-hook
Imports
- create
const create = require('gc-hook').create;import { create } from 'gc-hook'; - drop
const drop = require('gc-hook').drop;import { drop } from 'gc-hook';
Quickstart
import { create, drop } from 'gc-hook';
// Keep a count of all passed references created here
let references = 0;
// Notify how many references are still around once collected
const onGarbageCollected = myUtility => {
console.log(--references, 'references still used');
};
const createUtility = options => {
const myUtility = { ...options, do: 'something' };
console.log(++references, 'references provided');
// Return a proxy to avoid holding directly myUtility,
// while keeping the utility in memory until such proxy
// is not needed, used, or referenced anymore.
return create(myUtility, onGarbageCollected);
};
// As a module consumer:
let util = createUtility({ some: 'thing' });
console.log('Utility created:', util);
// Do something amazing with the util...
// Simulate releasing the utility after some time
setTimeout(() => {
// Clear the utility or don't reference it anymore anywhere
util = null;
console.log('Utility reference cleared. Awaiting GC...');
// Once the GC kicks in, the module.js will log how many
// utilities are still around and never collected.
// Note: GC is non-deterministic, so the log might not appear immediately.
}, 100);