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
#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
#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
#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
'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.