What are WeakRef and finalizers in ES2021 (ES12)
Asked Answered
O

2

6

I want to understand What are WeakRef and finalizers in ES2021 with a real simple example and Where to use them.

I know, WeakRef is a class. This will allow developers to create weak references to objects, while a finalizer or FinalizationRegistry allows you to register callback functions that will be invoked when an object is garbage collected

const myWeakRef = new WeakRef({
  name: 'Cache',
  size: 'unlimited'
})

// Log the value of "myWeakRef":
console.log(myWeakRef.deref())
Orthochromatic answered 20/3, 2021 at 15:34 Comment(1)
The proposal is on GitHub and documentation is on MDN. Are these explanations lacking in some way?Yenta
G
7

As always, MDN's docs help.

A WeakRef object contains a weak reference to an object, which is called its target or referent. A weak reference to an object is a reference that does not prevent the object from being reclaimed by the garbage collector. In contrast, a normal (or strong) reference keeps an object in memory. When an object no longer has any strong references to it, the JavaScript engine's garbage collector may destroy the object and reclaim its memory. If that happens, you can't get the object from a weak reference anymore.

In almost every other part of JS, if some object (A) holds a reference to another object (B), B will not be garbage-collected until A can be fully garbage-collected as well. For example:

// top level
const theA = {};
(() => {
  // private scope
  const theB = { foo: 'foo' };
  theA.obj = obj;
})();

In this situation, the theB will never be garbage collected (unless theA.obj gets reassigned) because theA on the top level contains a property that holds a reference to theB; it's a strong reference, which prevents garbage collection.

A WeakRef, on the other hand, provides a wrapper with access to an object while not preventing garbage collection of that object. Calling deref() on the WeakRef will return you the object if it hasn't been garbage collected yet. If it has been GC'd, .deref() will return undefined.


FinalizationRegistry deals with a similar issue:

A FinalizationRegistry object lets you request a callback when an object is garbage-collected.

You first define the registry with the callback you want to run, and then you call .register on the registry with the object you want to observe. This will let you know exactly when something gets garbage collected. For example, the following will log Just got GCd! once the obj gets reclaimed:

console.log('script starting...');

const r = new FinalizationRegistry(() => {
  console.log('Just got GCd!');
});
(() => {
  // private closure
  const obj = {};
  r.register(obj);
})();

You can also pass a value when calling .register that gets passed to the callback when the object gets collected.

new FinalizationRegistry((val) => {
  console.log(val);
});
r.register(obj, 'the object named "obj"')

will log the object named "obj" it gets GC'd.

All this said, there is rarely a need for these tools. As MDN says:

Correct use of FinalizationRegistry takes careful thought, and it's best avoided if possible. It's also important to avoid relying on any specific behaviors not guaranteed by the specification. When, how, and whether garbage collection occurs is down to the implementation of any given JavaScript engine. Any behavior you observe in one engine may be different in another engine, in another version of the same engine, or even in a slightly different situation with the same version of the same engine. Garbage collection is a hard problem that JavaScript engine implementers are constantly refining and improving their solutions to.

Best to let the engine itself deal with garbage collection automatically whenever possible, unless you have a really good reason to care about it yourself.

Grindelia answered 20/3, 2021 at 15:55 Comment(0)
O
1

The main use of weak references is to implement caches or mappings to large objects. In many scenarios, we don't want to keep a lot of memory for a long time saving this rarely used cache or mappings. We can allow the memory to be garbage collected soon and later if we need it again, we can generate a fresh cache. If the variable is no longer reachable, the JavaScript garbage collector automatically removes it.

const callback = () => {
  const aBigObj = {
    name: "Hello world"
  };
  console.log(aBigObj);
}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback();
      resolve();
    }, 2000);
  });
})();

When executing the above code, it prints "Hello world" after 2 seconds. Based on how we use the callback() function, aBigObj is stored in memory forever, maybe.

Let us make aBigObj a weak reference.

const callback = () => {
  const aBigObj = new WeakRef({    name: "Hello world"  });  console.log(aBigObj.deref().name);}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // Guaranteed to print "Hello world"
      resolve();
    }, 2000);
  });

  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // No Gaurantee that "Hello world" is printed
      resolve();
    }, 5000);
  });
})();

The first setTimeout() will surely print the value of the name. That is guaranteed in the first turn of the event loop after creating the weak reference.

But there is no guarantee that the second setTimeout() prints "Backbencher". It might have been sweeped by the garbage collector. Since the garbage collection works differently in different browsers, we cannot guarantee the output. That is also why we use WeakRef in situations like managing the cache.

More Information...

Orthochromatic answered 21/3, 2021 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.