jsguides

FinalizationRegistry

FinalizationRegistry lets you register cleanup callbacks that run when your objects are garbage-collected. It is available in all modern browsers and Node.js since April 2021.

Creating a registry

You instantiate FinalizationRegistry by passing a callback function:

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`Cleaning up: ${heldValue}`);
});

The callback receives whatever heldValue you passed when registering the target object.

Registering targets

Call register on any created registry to track an object:

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`Finalizing: ${heldValue}`);
});

const resource = { name: "database connection" };
registry.register(resource, "database connection");
// If `resource` becomes unreachable, the callback fires with "database connection"

When the target object has no remaining references and the garbage collector reclaims it, the callback fires asynchronously. The callback never runs synchronously during registration.

Register parameters

register(target, heldValue) takes two required arguments:

  • target — the object to track. This must be an object or a non-registered symbol.
  • heldValue — passed to your callback when the target is collected. Can be any value: primitives, objects, functions. The registry keeps a strong reference to objects here so it can deliver them to your callback later.

register(target, heldValue, unregisterToken) adds a third optional argument used to unregister the target before collection:

registry.register(resource, "database connection", resource);

If you omit the unregisterToken, the target cannot be unregistered later.

Return value and errors

register returns undefined. It throws a TypeError if:

  • target is not an object or a non-registered symbol
  • target === heldValue
  • unregisterToken is not an object or a non-registered symbol

Unregistering targets

If you need to cancel a registration before the target is collected, call unregister with the same token you passed as the third argument:

const registry = new FinalizationRegistry((heldValue) => {
  console.log(`Finalizing: ${heldValue}`);
});

const buffer = new ArrayBuffer(1024);
registry.register(buffer, "buffer", buffer);

// Later, when you're done with the buffer manually:
registry.unregister(buffer);
// Returns true — the registration was found and removed

unregister returns true if at least one registration matched the token and was removed, false if no registration matched. Multiple targets registered with the same token are unregistered together.

It throws a TypeError if unregisterToken is not an object or a non-registered symbol.

You only need unregister if you want to cancel a cleanup callback you previously registered. Do not call it from inside the cleanup callback itself.

Common usage pattern

FinalizationRegistry is useful for cleaning up native resources that JavaScript objects wrap:

class DatabaseConnection {
  static #registry = new FinalizationRegistry((id) => {
    console.log(`Closing orphaned connection: ${id}`);
  });

  #id;
  #connection;

  constructor(id) {
    this.#id = id;
    this.#connection = this.#connect();
    // Register cleanup using `this` as the unregister token
    DatabaseConnection.#registry.register(this, id, this);
  }

  #connect() {
    // actual connection logic
    return { id: this.#id, close: () => {} };
  }

  release() {
    this.#connection.close();
    // Cancel the finalizer — we already cleaned up
    DatabaseConnection.#registry.unregister(this);
  }
}

The registry does not hold a strong reference to the target itself (otherwise the target could never be garbage-collected). It holds a strong reference to the heldValue, which is how it delivers data to your callback after the target is gone.

What FinalizationRegistry does not do

The callback does not fire immediately when the last reference drops. Garbage collection is non-deterministic — it runs when the engine decides memory pressure warrants it. In some browsers, callbacks may not fire at all if the page is closed before collection.

Do not use FinalizationRegistry for critical program logic. Treat cleanup callbacks as best-effort hints that resources are being freed, not as guaranteed finalizers.

See Also

  • WeakRef — a weak reference that does not prevent garbage collection
  • WeakMap — a map holding weak references to its keys
  • WeakSet — a set holding weak references to its values
  • Memory management guide — how JavaScript manages memory and garbage collection