feat(napi): support async class factory (#1779)
- Close https://github.com/napi-rs/napi-rs/issues/1777
This commit is contained in:
parent
0dc1ef738b
commit
546b108a5b
11 changed files with 126 additions and 14 deletions
|
@ -112,8 +112,15 @@ impl TryToTokens for NapiFn {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// async factory only
|
||||||
|
let use_after_async = if self.is_async && self.parent.is_some() && self.fn_self.is_none() {
|
||||||
|
quote! { true }
|
||||||
|
} else {
|
||||||
|
quote! { false }
|
||||||
|
};
|
||||||
|
|
||||||
let function_call_inner = quote! {
|
let function_call_inner = quote! {
|
||||||
napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None).and_then(|mut cb| {
|
napi::bindgen_prelude::CallbackInfo::<#args_len>::new(env, cb, None, #use_after_async).and_then(|mut cb| {
|
||||||
#build_ref_container
|
#build_ref_container
|
||||||
#(#arg_conversions)*
|
#(#arg_conversions)*
|
||||||
#native_call
|
#native_call
|
||||||
|
@ -511,6 +518,8 @@ impl NapiFn {
|
||||||
if self.is_ret_result {
|
if self.is_ret_result {
|
||||||
if self.parent_is_generator {
|
if self.parent_is_generator {
|
||||||
quote! { cb.generator_factory(#js_name, #ret?) }
|
quote! { cb.generator_factory(#js_name, #ret?) }
|
||||||
|
} else if self.is_async {
|
||||||
|
quote! { cb.factory(#js_name, #ret) }
|
||||||
} else {
|
} else {
|
||||||
quote! { cb.factory(#js_name, #ret?) }
|
quote! { cb.factory(#js_name, #ret?) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -250,7 +250,7 @@ impl NapiStruct {
|
||||||
env: napi::bindgen_prelude::sys::napi_env,
|
env: napi::bindgen_prelude::sys::napi_env,
|
||||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||||
) -> napi::bindgen_prelude::sys::napi_value {
|
) -> napi::bindgen_prelude::sys::napi_value {
|
||||||
napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None)
|
napi::bindgen_prelude::CallbackInfo::<#fields_len>::new(env, cb, None, false)
|
||||||
.and_then(|cb| #constructor)
|
.and_then(|cb| #constructor)
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
|
unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
|
||||||
|
@ -669,7 +669,7 @@ impl NapiStruct {
|
||||||
env: napi::bindgen_prelude::sys::napi_env,
|
env: napi::bindgen_prelude::sys::napi_env,
|
||||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||||
) -> napi::bindgen_prelude::sys::napi_value {
|
) -> napi::bindgen_prelude::sys::napi_value {
|
||||||
napi::bindgen_prelude::CallbackInfo::<0>::new(env, cb, Some(0))
|
napi::bindgen_prelude::CallbackInfo::<0>::new(env, cb, Some(0), false)
|
||||||
.and_then(|mut cb| unsafe { cb.unwrap_borrow_mut::<#struct_name>() })
|
.and_then(|mut cb| unsafe { cb.unwrap_borrow_mut::<#struct_name>() })
|
||||||
.and_then(|obj| {
|
.and_then(|obj| {
|
||||||
#to_napi_value_convert
|
#to_napi_value_convert
|
||||||
|
@ -691,7 +691,7 @@ impl NapiStruct {
|
||||||
env: napi::bindgen_prelude::sys::napi_env,
|
env: napi::bindgen_prelude::sys::napi_env,
|
||||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||||
) -> napi::bindgen_prelude::sys::napi_value {
|
) -> napi::bindgen_prelude::sys::napi_value {
|
||||||
napi::bindgen_prelude::CallbackInfo::<1>::new(env, cb, Some(1))
|
napi::bindgen_prelude::CallbackInfo::<1>::new(env, cb, Some(1), false)
|
||||||
.and_then(|mut cb_info| unsafe {
|
.and_then(|mut cb_info| unsafe {
|
||||||
cb_info.unwrap_borrow_mut::<#struct_name>()
|
cb_info.unwrap_borrow_mut::<#struct_name>()
|
||||||
.and_then(|obj| {
|
.and_then(|obj| {
|
||||||
|
|
|
@ -226,7 +226,14 @@ impl NapiFn {
|
||||||
FnKind::Factory => self
|
FnKind::Factory => self
|
||||||
.parent
|
.parent
|
||||||
.clone()
|
.clone()
|
||||||
.map(|i| format!(": {}", i.to_string().to_case(Case::Pascal)))
|
.map(|i| {
|
||||||
|
let parent = i.to_string().to_case(Case::Pascal);
|
||||||
|
if self.is_async {
|
||||||
|
format!(": Promise<{}>", parent)
|
||||||
|
} else {
|
||||||
|
format!(": {}", parent)
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or_else(|| "".to_owned()),
|
.unwrap_or_else(|| "".to_owned()),
|
||||||
_ => {
|
_ => {
|
||||||
let ret = if let Some(ret) = &self.ret {
|
let ret = if let Some(ret) = &self.ret {
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub struct CallbackInfo<const N: usize> {
|
||||||
env: sys::napi_env,
|
env: sys::napi_env,
|
||||||
pub this: sys::napi_value,
|
pub this: sys::napi_value,
|
||||||
pub args: [sys::napi_value; N],
|
pub args: [sys::napi_value; N],
|
||||||
|
this_reference: sys::napi_ref,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> CallbackInfo<N> {
|
impl<const N: usize> CallbackInfo<N> {
|
||||||
|
@ -24,6 +25,9 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
env: sys::napi_env,
|
env: sys::napi_env,
|
||||||
callback_info: sys::napi_callback_info,
|
callback_info: sys::napi_callback_info,
|
||||||
required_argc: Option<usize>,
|
required_argc: Option<usize>,
|
||||||
|
// for async class factory, the `this` will be used after the async call
|
||||||
|
// so we must create reference for it and use it after async resolved
|
||||||
|
use_after_async: bool,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut this = ptr::null_mut();
|
let mut this = ptr::null_mut();
|
||||||
let mut args = [ptr::null_mut(); N];
|
let mut args = [ptr::null_mut(); N];
|
||||||
|
@ -55,7 +59,21 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { env, this, args })
|
let mut this_reference = ptr::null_mut();
|
||||||
|
|
||||||
|
if use_after_async {
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_create_reference(env, this, 1, &mut this_reference) },
|
||||||
|
"Failed to create reference for `this` in async class factory"
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
env,
|
||||||
|
this,
|
||||||
|
args,
|
||||||
|
this_reference,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_arg(&self, index: usize) -> sys::napi_value {
|
pub fn get_arg(&self, index: usize) -> sys::napi_value {
|
||||||
|
@ -141,8 +159,18 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
js_name: &str,
|
js_name: &str,
|
||||||
obj: T,
|
obj: T,
|
||||||
) -> Result<(sys::napi_value, *mut T)> {
|
) -> Result<(sys::napi_value, *mut T)> {
|
||||||
let this = self.this();
|
let mut this = self.this();
|
||||||
let mut instance = ptr::null_mut();
|
let mut instance = ptr::null_mut();
|
||||||
|
if !self.this_reference.is_null() {
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_get_reference_value(self.env, self.this_reference, &mut this) },
|
||||||
|
"Failed to get reference value for `this` in async class factory"
|
||||||
|
)?;
|
||||||
|
check_status!(
|
||||||
|
unsafe { sys::napi_delete_reference(self.env, self.this_reference) },
|
||||||
|
"Failed to delete reference for `this` in async class factory"
|
||||||
|
)?;
|
||||||
|
}
|
||||||
___CALL_FROM_FACTORY.with(|s| s.store(true, Ordering::Relaxed));
|
___CALL_FROM_FACTORY.with(|s| s.store(true, Ordering::Relaxed));
|
||||||
let status =
|
let status =
|
||||||
unsafe { sys::napi_new_instance(self.env, this, 0, ptr::null_mut(), &mut instance) };
|
unsafe { sys::napi_new_instance(self.env, this, 0, ptr::null_mut(), &mut instance) };
|
||||||
|
@ -154,6 +182,7 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
unsafe { sys::napi_throw(self.env, exception) };
|
unsafe { sys::napi_throw(self.env, exception) };
|
||||||
return Ok((ptr::null_mut(), ptr::null_mut()));
|
return Ok((ptr::null_mut(), ptr::null_mut()));
|
||||||
}
|
}
|
||||||
|
check_status!(status, "Failed to create instance of class `{}`", js_name)?;
|
||||||
let obj = Box::new(obj);
|
let obj = Box::new(obj);
|
||||||
let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
|
let initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
|
||||||
let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize))));
|
let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize))));
|
||||||
|
@ -164,7 +193,7 @@ impl<const N: usize> CallbackInfo<N> {
|
||||||
sys::napi_wrap(
|
sys::napi_wrap(
|
||||||
self.env,
|
self.env,
|
||||||
instance,
|
instance,
|
||||||
value_ref as *mut c_void,
|
value_ref.cast(),
|
||||||
Some(raw_finalize_unchecked::<T>),
|
Some(raw_finalize_unchecked::<T>),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
&mut object_ref,
|
&mut object_ref,
|
||||||
|
|
|
@ -1084,7 +1084,7 @@ impl Env {
|
||||||
T: 'static + Send,
|
T: 'static + Send,
|
||||||
V: 'static + ToNapiValue,
|
V: 'static + ToNapiValue,
|
||||||
F: 'static + Send + Future<Output = Result<T>>,
|
F: 'static + Send + Future<Output = Result<T>>,
|
||||||
R: 'static + Send + Sync + FnOnce(&mut Env, T) -> Result<V>,
|
R: 'static + Send + FnOnce(&mut Env, T) -> Result<V>,
|
||||||
>(
|
>(
|
||||||
&self,
|
&self,
|
||||||
fut: F,
|
fut: F,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{future::Future, sync::RwLock};
|
use std::{future::Future, marker::PhantomData, sync::RwLock};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
@ -83,11 +83,43 @@ pub fn within_runtime_if_available<F: FnOnce() -> T, T>(f: F) -> T {
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SendableResolver<
|
||||||
|
Data: 'static + Send,
|
||||||
|
R: 'static + FnOnce(sys::napi_env, Data) -> Result<sys::napi_value>,
|
||||||
|
> {
|
||||||
|
inner: R,
|
||||||
|
_data: PhantomData<Data>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `SendableResolver` will be only called in the `threadsafe_function_call_js` callback
|
||||||
|
// which means it will be always called in the Node.js JavaScript thread
|
||||||
|
// so the inner function is not required to be `Send`
|
||||||
|
// but the `Send` bound is required by the `execute_tokio_future` function
|
||||||
|
unsafe impl<Data: 'static + Send, R: 'static + FnOnce(sys::napi_env, Data) -> Result<sys::napi_value>>
|
||||||
|
Send for SendableResolver<Data, R>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Data: 'static + Send, R: 'static + FnOnce(sys::napi_env, Data) -> Result<sys::napi_value>>
|
||||||
|
SendableResolver<Data, R>
|
||||||
|
{
|
||||||
|
fn new(inner: R) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
_data: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve(self, env: sys::napi_env, data: Data) -> Result<sys::napi_value> {
|
||||||
|
(self.inner)(env, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
#[allow(clippy::not_unsafe_ptr_arg_deref)]
|
||||||
pub fn execute_tokio_future<
|
pub fn execute_tokio_future<
|
||||||
Data: 'static + Send,
|
Data: 'static + Send,
|
||||||
Fut: 'static + Send + Future<Output = Result<Data>>,
|
Fut: 'static + Send + Future<Output = Result<Data>>,
|
||||||
Resolver: 'static + Send + Sync + FnOnce(sys::napi_env, Data) -> Result<sys::napi_value>,
|
Resolver: 'static + FnOnce(sys::napi_env, Data) -> Result<sys::napi_value>,
|
||||||
>(
|
>(
|
||||||
env: sys::napi_env,
|
env: sys::napi_env,
|
||||||
fut: Fut,
|
fut: Fut,
|
||||||
|
@ -95,10 +127,14 @@ pub fn execute_tokio_future<
|
||||||
) -> Result<sys::napi_value> {
|
) -> Result<sys::napi_value> {
|
||||||
let (deferred, promise) = JsDeferred::new(env)?;
|
let (deferred, promise) = JsDeferred::new(env)?;
|
||||||
|
|
||||||
let inner = async move {
|
let sendable_resolver = SendableResolver::new(resolver);
|
||||||
|
|
||||||
|
let inner = async {
|
||||||
match fut.await {
|
match fut.await {
|
||||||
Ok(v) => deferred.resolve(|env| {
|
Ok(v) => deferred.resolve(move |env| {
|
||||||
resolver(env.raw(), v).map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) })
|
sendable_resolver
|
||||||
|
.resolve(env.raw(), v)
|
||||||
|
.map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) })
|
||||||
}),
|
}),
|
||||||
Err(e) => deferred.reject(e),
|
Err(e) => deferred.reject(e),
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,8 @@ Generated by [AVA](https://avajs.dev).
|
||||||
export class ClassWithFactory {␊
|
export class ClassWithFactory {␊
|
||||||
name: string␊
|
name: string␊
|
||||||
static withName(name: string): ClassWithFactory␊
|
static withName(name: string): ClassWithFactory␊
|
||||||
|
static with4Name(name: string): Promise<ClassWithFactory>␊
|
||||||
|
static with4NameResult(name: string): Promise<ClassWithFactory>␊
|
||||||
setName(name: string): this␊
|
setName(name: string): this␊
|
||||||
}␊
|
}␊
|
||||||
␊
|
␊
|
||||||
|
|
Binary file not shown.
|
@ -265,6 +265,13 @@ test('class factory', (t) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('async class factory', async (t) => {
|
||||||
|
const instance = await ClassWithFactory.with4Name('foo')
|
||||||
|
t.is(instance.name, 'foo-4')
|
||||||
|
const instance2 = await ClassWithFactory.with4NameResult('foo')
|
||||||
|
t.is(instance2.name, 'foo-4')
|
||||||
|
})
|
||||||
|
|
||||||
test('class constructor return Result', (t) => {
|
test('class constructor return Result', (t) => {
|
||||||
const c = new Context()
|
const c = new Context()
|
||||||
t.is(c.method(), 'not empty')
|
t.is(c.method(), 'not empty')
|
||||||
|
|
2
examples/napi/index.d.ts
vendored
2
examples/napi/index.d.ts
vendored
|
@ -92,6 +92,8 @@ export type Blake2bKey = Blake2BKey
|
||||||
export class ClassWithFactory {
|
export class ClassWithFactory {
|
||||||
name: string
|
name: string
|
||||||
static withName(name: string): ClassWithFactory
|
static withName(name: string): ClassWithFactory
|
||||||
|
static with4Name(name: string): Promise<ClassWithFactory>
|
||||||
|
static with4NameResult(name: string): Promise<ClassWithFactory>
|
||||||
setName(name: string): this
|
setName(name: string): this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
use napi::Result;
|
||||||
|
|
||||||
|
async fn always_4() -> i32 {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub struct ClassWithFactory {
|
pub struct ClassWithFactory {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -10,6 +16,20 @@ impl ClassWithFactory {
|
||||||
Self { name }
|
Self { name }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
pub async fn with_4_name(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name: format!("{name}-{}", always_4().await),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi(factory)]
|
||||||
|
pub async fn with_4_name_result(name: String) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: format!("{name}-{}", always_4().await),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn set_name(&mut self, name: String) -> &Self {
|
pub fn set_name(&mut self, name: String) -> &Self {
|
||||||
self.name = name;
|
self.name = name;
|
||||||
|
|
Loading…
Reference in a new issue