chore: add into_unknown; refactor tsfn api; add tests for js function
This commit is contained in:
parent
93f7180682
commit
472d4f2ab5
9 changed files with 102 additions and 27 deletions
|
@ -2,7 +2,7 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
|
||||||
use super::{JsObject, NapiValue, Value, ValueType};
|
use super::{JsObject, JsUnknown, NapiValue, Value, ValueType};
|
||||||
use crate::error::check_status;
|
use crate::error::check_status;
|
||||||
use crate::{sys, Result};
|
use crate::{sys, Result};
|
||||||
|
|
||||||
|
@ -13,6 +13,12 @@ pub struct JsBuffer {
|
||||||
pub len: u64,
|
pub len: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl JsBuffer {
|
||||||
|
pub fn into_unknown(self) -> Result<JsUnknown> {
|
||||||
|
self.value.into_unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NapiValue for JsBuffer {
|
impl NapiValue for JsBuffer {
|
||||||
fn raw_value(&self) -> sys::napi_value {
|
fn raw_value(&self) -> sys::napi_value {
|
||||||
self.value.0.value
|
self.value.0.value
|
||||||
|
|
|
@ -8,7 +8,22 @@ use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status};
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct JsFunction(pub(crate) Value);
|
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<JsNull> {
|
||||||
|
/// let js_func = ctx.get::<JsFunction>(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 {
|
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<JsUnknown> {
|
pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result<JsUnknown> {
|
||||||
let raw_this = this
|
let raw_this = this
|
||||||
.map(|v| v.into_raw())
|
.map(|v| v.into_raw())
|
||||||
|
|
|
@ -98,6 +98,9 @@ macro_rules! impl_js_value_methods {
|
||||||
pub fn into_raw(self) -> sys::napi_value {
|
pub fn into_raw(self) -> sys::napi_value {
|
||||||
self.0.value
|
self.0.value
|
||||||
}
|
}
|
||||||
|
pub fn into_unknown(self) -> Result<JsUnknown> {
|
||||||
|
JsUnknown::from_raw(self.0.env, self.0.value)
|
||||||
|
}
|
||||||
pub fn coerce_to_number(self) -> Result<JsNumber> {
|
pub fn coerce_to_number(self) -> Result<JsNumber> {
|
||||||
let mut new_raw_value = ptr::null_mut();
|
let mut new_raw_value = ptr::null_mut();
|
||||||
let status =
|
let status =
|
||||||
|
|
|
@ -2,16 +2,15 @@ use std::os::raw::{c_char, c_void};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use crate::error::check_status;
|
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_call_mode;
|
||||||
use sys::napi_threadsafe_function_release_mode;
|
use sys::napi_threadsafe_function_release_mode;
|
||||||
|
|
||||||
pub trait ToJs: Copy + Clone {
|
pub trait ToJs: Copy + Clone {
|
||||||
type Output;
|
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<Vec<JsUnknown>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Communicate with the addon's main thread by invoking a JavaScript function from other threads.
|
/// 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<T: ToJs>(
|
||||||
|
|
||||||
// Follow the convention of Node.js async callback.
|
// Follow the convention of Node.js async callback.
|
||||||
if ret.is_ok() {
|
if ret.is_ok() {
|
||||||
let (argv, js_value) = ret.unwrap();
|
let values = ret.unwrap();
|
||||||
let js_null = env.get_null().unwrap();
|
let js_null = env.get_null().unwrap();
|
||||||
let values = [js_null.0.value, js_value.raw_value()];
|
let mut raw_values: Vec<sys::napi_value> = vec![];
|
||||||
|
raw_values.push(js_null.into_raw());
|
||||||
|
for item in values.iter() {
|
||||||
|
raw_values.push(item.into_raw())
|
||||||
|
}
|
||||||
|
|
||||||
status = sys::napi_call_function(
|
status = sys::napi_call_function(
|
||||||
raw_env,
|
raw_env,
|
||||||
recv,
|
recv,
|
||||||
js_callback,
|
js_callback,
|
||||||
argv + 1,
|
(values.len() + 1) as u64,
|
||||||
values.as_ptr(),
|
raw_values.as_ptr(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
22
test_module/__test__/function.spec.js
Normal file
22
test_module/__test__/function.spec.js
Normal file
|
@ -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)
|
||||||
|
})
|
|
@ -5,11 +5,10 @@ test('should get js function called from a thread', async (t) => {
|
||||||
let called = 0
|
let called = 0
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
bindings.testThreadsafeFunction((err, ret) => {
|
bindings.testThreadsafeFunction((...args) => {
|
||||||
called += 1
|
called += 1
|
||||||
try {
|
try {
|
||||||
t.is(err, null)
|
t.deepEqual(args, [null, 42, 1, 2, 3])
|
||||||
t.is(ret, 42)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
}
|
}
|
||||||
|
|
22
test_module/src/function.rs
Normal file
22
test_module/src/function.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use napi::{JsFunction, CallContext, JsNull, Result, JsObject};
|
||||||
|
|
||||||
|
#[js_function(1)]
|
||||||
|
pub fn call_function(ctx: CallContext) -> Result<JsNull> {
|
||||||
|
let js_func = ctx.get::<JsFunction>(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<JsNull> {
|
||||||
|
let js_this = ctx.get::<JsObject>(0)?;
|
||||||
|
let js_func = ctx.get::<JsFunction>(1)?;
|
||||||
|
|
||||||
|
js_func.call(Some(&js_this), &[])?;
|
||||||
|
|
||||||
|
Ok(ctx.env.get_null()?)
|
||||||
|
}
|
|
@ -9,12 +9,14 @@ use napi::{CallContext, Error, JsString, JsUnknown, Module, Result, Status};
|
||||||
mod napi5;
|
mod napi5;
|
||||||
|
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
mod function;
|
||||||
mod external;
|
mod external;
|
||||||
mod symbol;
|
mod symbol;
|
||||||
mod task;
|
mod task;
|
||||||
mod tsfn;
|
mod tsfn;
|
||||||
|
|
||||||
use buffer::{buffer_to_string, get_buffer_length};
|
use buffer::{buffer_to_string, get_buffer_length};
|
||||||
|
use function::{call_function, call_function_with_this};
|
||||||
use external::{create_external, get_external_count};
|
use external::{create_external, get_external_count};
|
||||||
#[cfg(napi5)]
|
#[cfg(napi5)]
|
||||||
use napi5::is_date::test_object_is_date;
|
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("testTsfnError", test_tsfn_error)?;
|
||||||
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
|
module.create_named_method("testThreadsafeFunction", test_threadsafe_function)?;
|
||||||
module.create_named_method("testTokioReadfile", test_tokio_readfile)?;
|
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)]
|
#[cfg(napi5)]
|
||||||
module.create_named_method("testObjectIsDate", test_object_is_date)?;
|
module.create_named_method("testObjectIsDate", test_object_is_date)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -7,7 +7,8 @@ use napi::sys::{
|
||||||
};
|
};
|
||||||
use napi::threadsafe_function::{ThreadsafeFunction, ToJs};
|
use napi::threadsafe_function::{ThreadsafeFunction, ToJs};
|
||||||
use napi::{
|
use napi::{
|
||||||
CallContext, Env, Error, JsBuffer, JsFunction, JsNumber, JsString, JsUndefined, Result, Status,
|
CallContext, Env, Error, JsFunction, JsString, JsUndefined, Result, Status,
|
||||||
|
JsUnknown,
|
||||||
};
|
};
|
||||||
use tokio;
|
use tokio;
|
||||||
|
|
||||||
|
@ -15,15 +16,15 @@ use tokio;
|
||||||
struct HandleNumber;
|
struct HandleNumber;
|
||||||
|
|
||||||
impl ToJs for HandleNumber {
|
impl ToJs for HandleNumber {
|
||||||
type Output = u8;
|
type Output = Vec<u8>;
|
||||||
type JsValue = JsNumber;
|
|
||||||
|
|
||||||
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, Self::JsValue)> {
|
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Vec<JsUnknown>> {
|
||||||
let argv: u64 = 1;
|
let mut items: Vec<JsUnknown> = vec![];
|
||||||
|
for item in output.iter() {
|
||||||
let value = env.create_uint32(output as u32)?;
|
let value = env.create_uint32((*item) as u32)?.into_unknown()?;
|
||||||
|
items.push(value);
|
||||||
Ok((argv, value))
|
}
|
||||||
|
Ok(items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,10 +36,10 @@ pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
||||||
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
|
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
let output: u8 = 42;
|
let output: Vec<u8> = vec![42, 1, 2, 3];
|
||||||
// It's okay to call a threadsafe function multiple times.
|
// It's okay to call a threadsafe function multiple times.
|
||||||
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
|
tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap();
|
||||||
tsfn.call(Ok(output), napi_tsfn_blocking).unwrap();
|
tsfn.call(Ok(output.clone()), napi_tsfn_blocking).unwrap();
|
||||||
tsfn.release(napi_tsfn_release).unwrap();
|
tsfn.release(napi_tsfn_release).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -69,11 +70,10 @@ struct HandleBuffer;
|
||||||
|
|
||||||
impl ToJs for HandleBuffer {
|
impl ToJs for HandleBuffer {
|
||||||
type Output = Vec<u8>;
|
type Output = Vec<u8>;
|
||||||
type JsValue = JsBuffer;
|
|
||||||
|
|
||||||
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<(u64, JsBuffer)> {
|
fn resolve(&self, env: &mut Env, output: Self::Output) -> Result<Vec<JsUnknown>> {
|
||||||
let value = env.create_buffer_with_data(output.to_vec())?;
|
let value = env.create_buffer_with_data(output.to_vec())?.into_unknown()?;
|
||||||
Ok((1u64, value))
|
Ok(vec![value])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue