Reference
Ref
is very similar to Rc
in Rust. However it will not decrease the ref count when dropped, you need to call the unref
method on it manually.
In some scenarios, you may need to manually extend the lifetime of certain JavaScript objects to prevent them from being reclaimed by the GC prematurely. Unlike using objects in JavaScript, keeping a reference or value of a JsObject
in Rust is opaque to the Node.js GC system, meaning that the GC doesn't know that you still have an Object value that you want to use at some point in the future. GC will ruthlessly recycle it when it sees fit. At this point we need to create a Reference for this object, which is equivalent to telling the Node.js GC system: hey I still need this object, don't recycle it yet.
We usually need to manually extend the lifetime of an Object when doing some asynchronous operations, such as storing the value of some object in Task
or ThreadSafeFunction
. These objects can't be destroyed after the function finishes executing, so we need to wait until the asynchronous task finishes executing and then manually call the unref
method to tell the GC that we don't need the object anymore.
struct Hash(Ref<JsBufferValue>);
impl Task for Hash {
type Output = String;
type JsValue = JsString;
fn compute(&mut self) -> Result<Self::Output> {
Ok(base64(&self.0))
}
fn resolve(self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
let result = env.create_string_from_std(output);
self.0.unref(env)?;
result
}
}
#[js_function(1)]
// return Promise Object
fn async_hash(ctx: CallContext) -> Result<JsObject> {
let input_data = ctx.get::<JsBuffer>(0)?.into_ref()?;
let hash = Hash(input_data);
ctx.env.spawn(hash).map(|async_task| async_task.promise_object())
}
fn base64(data: &[u8]) -> String {
todo!();
}
asyncHash(Buffer::from([1, 2])).then((result) => {
console.log(result) // 0102
})
For JavaScript objects other than JsBuffer
, you can use Env::create_reference
to create references for them and fetch these JavaScript objects back with Env::get_reference_value
later. For example:
struct CallbackContext {
callback: Ref<()>
}
#[napi]
pub fn wrap_in_obj(env: Env, js_fn: JsFunction) -> Result<JsObject> {
let mut js_obj = env.create_object()?;
// create a reference for the javascript function
let js_fn_ref = env.create_reference(js_fn)?;
let ctx = CallbackContext {
callback: js_fn_ref,
};
// wrap it in an object
env.wrap(&mut js_obj, ctx)?;
Ok(js_obj)
}
#[napi]
pub fn call_wrapped_fn(env: Env, js_obj: JsObject) -> Result<()> {
let ctx: &mut CallbackContext = env.unwrap(&js_obj)?;
let js_fn: JsFunction = env.get_reference_value(&ctx.callback)?;
// the javascript function should not be reclaimed before we call Ref::unref()
js_fn.call_without_args(None)?;
Ok(())
}
const logSomething = () => {
console.log('hello')
}
const obj = wrapInObj(logSomething)
callWrappedFn(obj) // log 'hello'
You should always get JavaScript objects from references instead of caching these JavaScript objects directly. Referenced JsValue
could still become invalid after some time.