chore: add into_unknown; refactor tsfn api; add tests for js function

This commit is contained in:
Ouyang Yadong 2020-07-03 00:36:45 +08:00
parent 93f7180682
commit 472d4f2ab5
9 changed files with 102 additions and 27 deletions

View file

@ -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

View file

@ -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())

View file

@ -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 =

View file

@ -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 {

View 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)
})

View file

@ -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)
} }

View 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()?)
}

View file

@ -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(())

View file

@ -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])
} }
} }