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! {
|
||||
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
|
||||
#(#arg_conversions)*
|
||||
#native_call
|
||||
|
@ -511,6 +518,8 @@ impl NapiFn {
|
|||
if self.is_ret_result {
|
||||
if self.parent_is_generator {
|
||||
quote! { cb.generator_factory(#js_name, #ret?) }
|
||||
} else if self.is_async {
|
||||
quote! { cb.factory(#js_name, #ret) }
|
||||
} else {
|
||||
quote! { cb.factory(#js_name, #ret?) }
|
||||
}
|
||||
|
|
|
@ -250,7 +250,7 @@ impl NapiStruct {
|
|||
env: napi::bindgen_prelude::sys::napi_env,
|
||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||
) -> 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)
|
||||
.unwrap_or_else(|e| {
|
||||
unsafe { napi::bindgen_prelude::JsError::from(e).throw_into(env) };
|
||||
|
@ -669,7 +669,7 @@ impl NapiStruct {
|
|||
env: napi::bindgen_prelude::sys::napi_env,
|
||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||
) -> 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(|obj| {
|
||||
#to_napi_value_convert
|
||||
|
@ -691,7 +691,7 @@ impl NapiStruct {
|
|||
env: napi::bindgen_prelude::sys::napi_env,
|
||||
cb: napi::bindgen_prelude::sys::napi_callback_info
|
||||
) -> 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 {
|
||||
cb_info.unwrap_borrow_mut::<#struct_name>()
|
||||
.and_then(|obj| {
|
||||
|
|
|
@ -226,7 +226,14 @@ impl NapiFn {
|
|||
FnKind::Factory => self
|
||||
.parent
|
||||
.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()),
|
||||
_ => {
|
||||
let ret = if let Some(ret) = &self.ret {
|
||||
|
|
|
@ -16,6 +16,7 @@ pub struct CallbackInfo<const N: usize> {
|
|||
env: sys::napi_env,
|
||||
pub this: sys::napi_value,
|
||||
pub args: [sys::napi_value; N],
|
||||
this_reference: sys::napi_ref,
|
||||
}
|
||||
|
||||
impl<const N: usize> CallbackInfo<N> {
|
||||
|
@ -24,6 +25,9 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
env: sys::napi_env,
|
||||
callback_info: sys::napi_callback_info,
|
||||
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> {
|
||||
let mut this = ptr::null_mut();
|
||||
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 {
|
||||
|
@ -141,8 +159,18 @@ impl<const N: usize> CallbackInfo<N> {
|
|||
js_name: &str,
|
||||
obj: T,
|
||||
) -> Result<(sys::napi_value, *mut T)> {
|
||||
let this = self.this();
|
||||
let mut this = self.this();
|
||||
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));
|
||||
let status =
|
||||
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) };
|
||||
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 initial_finalize: Box<dyn FnOnce()> = Box::new(|| {});
|
||||
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(
|
||||
self.env,
|
||||
instance,
|
||||
value_ref as *mut c_void,
|
||||
value_ref.cast(),
|
||||
Some(raw_finalize_unchecked::<T>),
|
||||
ptr::null_mut(),
|
||||
&mut object_ref,
|
||||
|
|
|
@ -1084,7 +1084,7 @@ impl Env {
|
|||
T: 'static + Send,
|
||||
V: 'static + ToNapiValue,
|
||||
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,
|
||||
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 tokio::runtime::Runtime;
|
||||
|
@ -83,11 +83,43 @@ pub fn within_runtime_if_available<F: FnOnce() -> T, T>(f: F) -> T {
|
|||
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)]
|
||||
pub fn execute_tokio_future<
|
||||
Data: 'static + Send,
|
||||
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,
|
||||
fut: Fut,
|
||||
|
@ -95,10 +127,14 @@ pub fn execute_tokio_future<
|
|||
) -> Result<sys::napi_value> {
|
||||
let (deferred, promise) = JsDeferred::new(env)?;
|
||||
|
||||
let inner = async move {
|
||||
let sendable_resolver = SendableResolver::new(resolver);
|
||||
|
||||
let inner = async {
|
||||
match fut.await {
|
||||
Ok(v) => deferred.resolve(|env| {
|
||||
resolver(env.raw(), v).map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) })
|
||||
Ok(v) => deferred.resolve(move |env| {
|
||||
sendable_resolver
|
||||
.resolve(env.raw(), v)
|
||||
.map(|v| unsafe { JsUnknown::from_raw_unchecked(env.raw(), v) })
|
||||
}),
|
||||
Err(e) => deferred.reject(e),
|
||||
}
|
||||
|
|
|
@ -102,6 +102,8 @@ Generated by [AVA](https://avajs.dev).
|
|||
export class ClassWithFactory {␊
|
||||
name: string␊
|
||||
static withName(name: string): ClassWithFactory␊
|
||||
static with4Name(name: string): Promise<ClassWithFactory>␊
|
||||
static with4NameResult(name: string): Promise<ClassWithFactory>␊
|
||||
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) => {
|
||||
const c = new Context()
|
||||
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 {
|
||||
name: string
|
||||
static withName(name: string): ClassWithFactory
|
||||
static with4Name(name: string): Promise<ClassWithFactory>
|
||||
static with4NameResult(name: string): Promise<ClassWithFactory>
|
||||
setName(name: string): this
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
use napi::Result;
|
||||
|
||||
async fn always_4() -> i32 {
|
||||
4
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct ClassWithFactory {
|
||||
pub name: String,
|
||||
|
@ -10,6 +16,20 @@ impl ClassWithFactory {
|
|||
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]
|
||||
pub fn set_name(&mut self, name: String) -> &Self {
|
||||
self.name = name;
|
||||
|
|
Loading…
Reference in a new issue