diff --git a/napi/src/js_values/buffer.rs b/napi/src/js_values/buffer.rs index bda2fc44..dfe082c0 100644 --- a/napi/src/js_values/buffer.rs +++ b/napi/src/js_values/buffer.rs @@ -2,7 +2,7 @@ use std::ops::{Deref, DerefMut}; use std::ptr; use std::slice; -use super::{JsObject, NapiValue, Value, ValueType}; +use super::{JsObject, JsUnknown, NapiValue, Value, ValueType}; use crate::error::check_status; use crate::{sys, Result}; @@ -13,6 +13,12 @@ pub struct JsBuffer { pub len: u64, } +impl JsBuffer { + pub fn into_unknown(self) -> Result { + self.value.into_unknown() + } +} + impl NapiValue for JsBuffer { fn raw_value(&self) -> sys::napi_value { self.value.0.value diff --git a/napi/src/js_values/function.rs b/napi/src/js_values/function.rs index 03c2297a..d2b6db82 100644 --- a/napi/src/js_values/function.rs +++ b/napi/src/js_values/function.rs @@ -8,7 +8,22 @@ use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status}; #[derive(Clone, Copy, Debug)] pub struct JsFunction(pub(crate) Value); +/// See [Working with JavaScript Functions](https://nodejs.org/api/n-api.html#n_api_working_with_javascript_functions). +/// +/// Example: +/// ``` +/// use napi::{JsFunction, CallContext, JsNull, Result}; +/// +/// #[js_function(1)] +/// pub fn call_function(ctx: CallContext) -> Result { +/// let js_func = ctx.get::(0)?; +/// let js_string = ctx.env.create_string("hello".as_ref())?.into_unknown()?; +/// js_func.call(None, &[js_string])?; +/// Ok(ctx.env.get_null()?) +/// } +/// ``` impl JsFunction { + /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result { let raw_this = this .map(|v| v.into_raw()) diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index be264412..84e4abad 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -98,6 +98,9 @@ macro_rules! impl_js_value_methods { pub fn into_raw(self) -> sys::napi_value { self.0.value } + pub fn into_unknown(self) -> Result { + JsUnknown::from_raw(self.0.env, self.0.value) + } pub fn coerce_to_number(self) -> Result { let mut new_raw_value = ptr::null_mut(); let status = diff --git a/napi/src/threadsafe_function.rs b/napi/src/threadsafe_function.rs index f505de30..8e4ac1f3 100644 --- a/napi/src/threadsafe_function.rs +++ b/napi/src/threadsafe_function.rs @@ -2,16 +2,15 @@ use std::os::raw::{c_char, c_void}; use std::ptr; use crate::error::check_status; -use crate::{sys, Env, JsFunction, NapiValue, Result}; +use crate::{sys, Env, JsFunction, JsUnknown, Result}; use sys::napi_threadsafe_function_call_mode; use sys::napi_threadsafe_function_release_mode; pub trait ToJs: Copy + Clone { type Output; - type JsValue: NapiValue; - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)>; + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result>; } /// Communicate with the addon's main thread by invoking a JavaScript function from other threads. @@ -205,15 +204,20 @@ unsafe extern "C" fn call_js_cb( // Follow the convention of Node.js async callback. if ret.is_ok() { - let (argv, js_value) = ret.unwrap(); + let values = ret.unwrap(); let js_null = env.get_null().unwrap(); - let values = [js_null.0.value, js_value.raw_value()]; + let mut raw_values: Vec = vec![]; + raw_values.push(js_null.into_raw()); + for item in values.iter() { + raw_values.push(item.into_raw()) + } + status = sys::napi_call_function( raw_env, recv, js_callback, - argv + 1, - values.as_ptr(), + (values.len() + 1) as u64, + raw_values.as_ptr(), ptr::null_mut(), ); } else { diff --git a/test_module/__test__/function.spec.js b/test_module/__test__/function.spec.js new file mode 100644 index 00000000..103b8b7b --- /dev/null +++ b/test_module/__test__/function.spec.js @@ -0,0 +1,22 @@ +const test = require('ava') + +const bindings = require('../index.node') + +test('should call the function', async (t) => { + const ret = await new Promise((resolve) => { + bindings.testCallFunction((arg1, arg2) => { + resolve(`${arg1} ${arg2}`) + }) + }) + t.is(ret, 'hello world') +}) + +test('should set "this" properly', async (t) => { + const obj = {} + const ret = await new Promise((resolve) => { + bindings.testCallFunctionWithThis(obj, function () { + resolve(this) + }) + }) + t.is(ret, obj) +}) diff --git a/test_module/__test__/threadsafe_function.spec.js b/test_module/__test__/threadsafe_function.spec.js index 627b2952..eac5de27 100644 --- a/test_module/__test__/threadsafe_function.spec.js +++ b/test_module/__test__/threadsafe_function.spec.js @@ -5,11 +5,10 @@ test('should get js function called from a thread', async (t) => { let called = 0 return new Promise((resolve, reject) => { - bindings.testThreadsafeFunction((err, ret) => { + bindings.testThreadsafeFunction((...args) => { called += 1 try { - t.is(err, null) - t.is(ret, 42) + t.deepEqual(args, [null, 42, 1, 2, 3]) } catch (err) { reject(err) } diff --git a/test_module/src/function.rs b/test_module/src/function.rs new file mode 100644 index 00000000..6e7a13ec --- /dev/null +++ b/test_module/src/function.rs @@ -0,0 +1,22 @@ +use napi::{JsFunction, CallContext, JsNull, Result, JsObject}; + +#[js_function(1)] +pub fn call_function(ctx: CallContext) -> Result { + let js_func = ctx.get::(0)?; + let js_string_hello = ctx.env.create_string("hello".as_ref())?.into_unknown()?; + let js_string_world = ctx.env.create_string("world".as_ref())?.into_unknown()?; + + js_func.call(None, &[js_string_hello, js_string_world])?; + + Ok(ctx.env.get_null()?) +} + +#[js_function(2)] +pub fn call_function_with_this(ctx: CallContext) -> Result { + let js_this = ctx.get::(0)?; + let js_func = ctx.get::(1)?; + + js_func.call(Some(&js_this), &[])?; + + Ok(ctx.env.get_null()?) +} diff --git a/test_module/src/lib.rs b/test_module/src/lib.rs index 757bd7f0..bcf0847c 100644 --- a/test_module/src/lib.rs +++ b/test_module/src/lib.rs @@ -9,12 +9,14 @@ use napi::{CallContext, Error, JsString, JsUnknown, Module, Result, Status}; mod napi5; mod buffer; +mod function; mod external; mod symbol; mod task; mod tsfn; use buffer::{buffer_to_string, get_buffer_length}; +use function::{call_function, call_function_with_this}; use external::{create_external, get_external_count}; #[cfg(napi5)] use napi5::is_date::test_object_is_date; @@ -38,6 +40,8 @@ fn init(module: &mut Module) -> Result<()> { module.create_named_method("testTsfnError", test_tsfn_error)?; module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?; module.create_named_method("testTokioReadfile", test_tokio_readfile)?; + module.create_named_method("testCallFunction", call_function)?; + module.create_named_method("testCallFunctionWithThis", call_function_with_this)?; #[cfg(napi5)] module.create_named_method("testObjectIsDate", test_object_is_date)?; Ok(()) diff --git a/test_module/src/tsfn.rs b/test_module/src/tsfn.rs index 87251759..459cb22c 100644 --- a/test_module/src/tsfn.rs +++ b/test_module/src/tsfn.rs @@ -7,7 +7,8 @@ use napi::sys::{ }; use napi::threadsafe_function::{ThreadsafeFunction, ToJs}; use napi::{ - CallContext, Env, Error, JsBuffer, JsFunction, JsNumber, JsString, JsUndefined, Result, Status, + CallContext, Env, Error, JsFunction, JsString, JsUndefined, Result, Status, + JsUnknown, }; use tokio; @@ -15,15 +16,15 @@ use tokio; struct HandleNumber; impl ToJs for HandleNumber { - type Output = u8; - type JsValue = JsNumber; + type Output = Vec; - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)> { - let argv: u64 = 1; - - let value = env.create_uint32(output as u32)?; - - Ok((argv, value)) + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result> { + let mut items: Vec = vec![]; + for item in output.iter() { + let value = env.create_uint32((*item) as u32)?.into_unknown()?; + items.push(value); + } + Ok(items) } } @@ -35,10 +36,10 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result { let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?; thread::spawn(move || { - let output: u8 = 42; + let output: Vec = vec![42, 1, 2, 3]; // It's okay to call a threadsafe function multiple times. - tsfn.call(Ok(output), napi_tsfn_blocking).unwrap(); - tsfn.call(Ok(output), napi_tsfn_blocking).unwrap(); + tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap(); + tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap(); tsfn.release(napi_tsfn_release).unwrap(); }); @@ -69,11 +70,10 @@ struct HandleBuffer; impl ToJs for HandleBuffer { type Output = Vec; - type JsValue = JsBuffer; - fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, JsBuffer)> { - let value = env.create_buffer_with_data(output.to_vec())?; - Ok((1u64, value)) + fn resolve(&self, env: &mut Env, output: Self::Output) -> Result> { + let value = env.create_buffer_with_data(output.to_vec())?.into_unknown()?; + Ok(vec![value]) } }