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::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<JsUnknown> {
|
||||
self.value.into_unknown()
|
||||
}
|
||||
}
|
||||
|
||||
impl NapiValue for JsBuffer {
|
||||
fn raw_value(&self) -> sys::napi_value {
|
||||
self.value.0.value
|
||||
|
|
|
@ -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<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 {
|
||||
/// [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> {
|
||||
let raw_this = this
|
||||
.map(|v| v.into_raw())
|
||||
|
|
|
@ -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> {
|
||||
JsUnknown::from_raw(self.0.env, self.0.value)
|
||||
}
|
||||
pub fn coerce_to_number(self) -> Result<JsNumber> {
|
||||
let mut new_raw_value = ptr::null_mut();
|
||||
let status =
|
||||
|
|
|
@ -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<Vec<JsUnknown>>;
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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<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(
|
||||
raw_env,
|
||||
recv,
|
||||
js_callback,
|
||||
argv + 1,
|
||||
values.as_ptr(),
|
||||
(values.len() + 1) as u64,
|
||||
raw_values.as_ptr(),
|
||||
ptr::null_mut(),
|
||||
);
|
||||
} 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
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
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 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(())
|
||||
|
|
|
@ -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<u8>;
|
||||
|
||||
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<Vec<JsUnknown>> {
|
||||
let mut items: Vec<JsUnknown> = 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<JsUndefined> {
|
|||
let tsfn = ThreadsafeFunction::create(ctx.env, func, to_js, 0)?;
|
||||
|
||||
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.
|
||||
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<u8>;
|
||||
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<Vec<JsUnknown>> {
|
||||
let value = env.create_buffer_with_data(output.to_vec())?.into_unknown()?;
|
||||
Ok(vec![value])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue