Skip to content

Object and function references

Background

JavaScript implements a dynamic memory model. When objects are no longer being used, they are automatically deleted by the garbage collector running in the background. JavaScript maintains a reference count to objects in memory to determine whether an object is still in use or not. When the reference count goes to zero, the memory for the object becomes eligible for deletion by the garbage collector.

There are situations when you need to insure that objects created by your Node-API code remain allocated. In this case you need to explicitly create a reference to it. This is the purpose of the ObjectReference and ObjectFunction classes.

Persistent Reference

Object and function references can be instantiated as either Weak or Persistent.

A Persistent reference initializes the internal reference count to one which prevents reclamation of the object’s memory by the garbage collector. The referenced object will remain in memory for the life of the Persistent reference.

Using a Persistent reference makes sense in the case where the duration of the reference is known ahead of time. The internal reference count is decremented when the Persistent reference is deleted. This will make the referenced object eligible for deletion of the internal reference count goes to zero.

The most common case for using a Persistent reference is when you create a JavaScript class in your Node-API code and you need to insure its constructor remains allocated by the JavaScript runtime engine.

Weak Reference

For more complex implementations where multiple AsyncWorkers rely of the referenced object, it may make better sense to use a Weak reference which initializes the internal reference count to zero. The reference count can then be maintained using the Reference Ref and Unref methods.

The most common use case for a Weak reference is when your Node-API code needs to monitor when a JavaScript object you’ve created in your Node-API code is released by the garbage collector.

ObjectReference

The ObjectReference class inherits from the Reference class. The value it adds is a collection of Get and Set methods that manipulate the referenced object’s properties.

FunctionReference

Like the ObjectReference, the FunctionReference class inherits from the Reference class. While the ObjectReference class adds the Get and Set methods, the FunctionReference class adds a set of Call methods that implement calls to the function.

Example

This example code shows how to use the FunctionReference class.

src/native-addon.h

native-addon.h

#include <napi.h>

class NativeAddon : public Napi::ObjectWrap<NativeAddon> {
 public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
  NativeAddon(const Napi::CallbackInfo& info);

 private:
  static Napi::FunctionReference constructor;
  Napi::FunctionReference jsFnRef;
  Napi::Function jsFn;

  void TryCallByStoredReference(const Napi::CallbackInfo& info);
  void TryCallByStoredFunction(const Napi::CallbackInfo& info);
};

The NativeAddon C++ class has two data members that are populated in the NativeAddon constructor:

  • Napi::FunctionReference jsFnRef
  • Napi::Function jsFn

src/native-addon.cc

native-addon.cc

#include "native-addon.h"
#include <iostream>

Napi::FunctionReference NativeAddon::constructor;

Napi::Object NativeAddon::Init(Napi::Env env, Napi::Object exports) {
  Napi::Function func =
      DefineClass(env,
                  "NativeAddon",
                  {InstanceMethod("tryCallByStoredReference",
                                  &NativeAddon::TryCallByStoredReference),
                   InstanceMethod("tryCallByStoredFunction",
                                  &NativeAddon::TryCallByStoredFunction)});

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("NativeAddon", func);
  return exports;
}

NativeAddon::NativeAddon(const Napi::CallbackInfo& info)
    : Napi::ObjectWrap<NativeAddon>(info) {
  jsFnRef = Napi::Persistent(info[0].As<Napi::Function>());
  jsFn = info[1].As<Napi::Function>();
}

void NativeAddon::TryCallByStoredReference(const Napi::CallbackInfo& info) {
  // Napi::Env env = info.Env();
  jsFnRef.Call({});
}

void NativeAddon::TryCallByStoredFunction(const Napi::CallbackInfo& info) {
  // Napi::Env env = info.Env();
  jsFn.Call({});
}

The NativeAddon constructor, which is called from JavaScript, takes two function arguments. The first argument is stored as a Napi::FunctionReference and the second is stored as a Napi::Function.

There is a deliberate error in this code.

The second function is stored in the Napi::Function jsFn data member. This is an error because the lifetime of the second argument is limited to the lifetime of the constructor. The value of the jsFn data member will be invalid after the constructor returns. The first argument is stored in the Napi::FunctionReference jsFnRef. Because of the use of the Napi::FunctionReference, the value of jsFnRef will remain valid after the constructor returns.

The NativeAddon class implements two methods which can be called from JavaScript: TryCallByStoredReference and TryCallByStoredFunction. Notice that the Call method is used the same way for both the jsFnRef and jsFn data members.

src/binding.cc

binding.cc

#include <napi.h>
#include "native-addon.h"

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  NativeAddon::Init(env, exports);
  return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

This is a standard binding.cc file:

index.js

index.js

'use strict'

const { NativeAddon } = require('bindings')('addon')

function JSFnRef() {
    console.log('Hi! I\'m a JS function reference!')
}

function JSFn() {
    console.log('Hi! I\'m a JS function!')
}

const ITERATIONS = 5

const addon = new NativeAddon(JSFnRef, JSFn)

for (let i = 0; i < ITERATIONS; i++) {
    addon.tryCallByStoredReference()
}

try {
    addon.tryCallByStoredFunction()
} catch (err) {
    console.error('This code crashes because JSFn is valid only inside the constructor function.')
    console.error('The lifespan of the JSFn function is limited to the execution of the constructor function.')
    console.error('After that, the function stored in JSFn is ready to be garbage collected and it cannot be used anymore.')
}

This JavaScript code shows the use of the NativeAddon class. Note that the call to the native tryCallByStoredFunction method fails because the data member on which it relies is not valid.

Resources

ObjectReference documentation.

FunctionReference documentation.