diff --git a/crates/backend/src/codegen/fn.rs b/crates/backend/src/codegen/fn.rs index 9bf200b4..0dba1c26 100644 --- a/crates/backend/src/codegen/fn.rs +++ b/crates/backend/src/codegen/fn.rs @@ -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?) } } diff --git a/crates/backend/src/codegen/struct.rs b/crates/backend/src/codegen/struct.rs index bdbd9304..b39103a0 100644 --- a/crates/backend/src/codegen/struct.rs +++ b/crates/backend/src/codegen/struct.rs @@ -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| { diff --git a/crates/backend/src/typegen/fn.rs b/crates/backend/src/typegen/fn.rs index 1d261c0c..a7fa7bff 100644 --- a/crates/backend/src/typegen/fn.rs +++ b/crates/backend/src/typegen/fn.rs @@ -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 { diff --git a/crates/napi/src/bindgen_runtime/callback_info.rs b/crates/napi/src/bindgen_runtime/callback_info.rs index b41ce559..54e03968 100644 --- a/crates/napi/src/bindgen_runtime/callback_info.rs +++ b/crates/napi/src/bindgen_runtime/callback_info.rs @@ -16,6 +16,7 @@ pub struct CallbackInfo { env: sys::napi_env, pub this: sys::napi_value, pub args: [sys::napi_value; N], + this_reference: sys::napi_ref, } impl CallbackInfo { @@ -24,6 +25,9 @@ impl CallbackInfo { env: sys::napi_env, callback_info: sys::napi_callback_info, required_argc: Option, + // 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 { let mut this = ptr::null_mut(); let mut args = [ptr::null_mut(); N]; @@ -55,7 +59,21 @@ impl CallbackInfo { } } - 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 CallbackInfo { 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 CallbackInfo { 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 = Box::new(|| {}); let finalize_callbacks_ptr = Rc::into_raw(Rc::new(Cell::new(Box::into_raw(initial_finalize)))); @@ -164,7 +193,7 @@ impl CallbackInfo { sys::napi_wrap( self.env, instance, - value_ref as *mut c_void, + value_ref.cast(), Some(raw_finalize_unchecked::), ptr::null_mut(), &mut object_ref, diff --git a/crates/napi/src/env.rs b/crates/napi/src/env.rs index b0b43374..1b5c59dd 100644 --- a/crates/napi/src/env.rs +++ b/crates/napi/src/env.rs @@ -1084,7 +1084,7 @@ impl Env { T: 'static + Send, V: 'static + ToNapiValue, F: 'static + Send + Future>, - R: 'static + Send + Sync + FnOnce(&mut Env, T) -> Result, + R: 'static + Send + FnOnce(&mut Env, T) -> Result, >( &self, fut: F, diff --git a/crates/napi/src/tokio_runtime.rs b/crates/napi/src/tokio_runtime.rs index c56c1a69..c89fa99b 100644 --- a/crates/napi/src/tokio_runtime.rs +++ b/crates/napi/src/tokio_runtime.rs @@ -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 T, T>(f: F) -> T { f() } +struct SendableResolver< + Data: 'static + Send, + R: 'static + FnOnce(sys::napi_env, Data) -> Result, +> { + inner: R, + _data: PhantomData, +} + +// 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 Result> + Send for SendableResolver +{ +} + +impl Result> + SendableResolver +{ + fn new(inner: R) -> Self { + Self { + inner, + _data: PhantomData, + } + } + + fn resolve(self, env: sys::napi_env, data: Data) -> Result { + (self.inner)(env, data) + } +} + #[allow(clippy::not_unsafe_ptr_arg_deref)] pub fn execute_tokio_future< Data: 'static + Send, Fut: 'static + Send + Future>, - Resolver: 'static + Send + Sync + FnOnce(sys::napi_env, Data) -> Result, + Resolver: 'static + FnOnce(sys::napi_env, Data) -> Result, >( env: sys::napi_env, fut: Fut, @@ -95,10 +127,14 @@ pub fn execute_tokio_future< ) -> Result { 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), } diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index ad5833cf..7951d2ec 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -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␊ + static with4NameResult(name: string): Promise␊ setName(name: string): this␊ }␊ ␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 21386263..92bf0312 100644 Binary files a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap and b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap differ diff --git a/examples/napi/__tests__/values.spec.ts b/examples/napi/__tests__/values.spec.ts index b562a1d8..2463e568 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -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') diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index c3a0e08d..eb7075d9 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -92,6 +92,8 @@ export type Blake2bKey = Blake2BKey export class ClassWithFactory { name: string static withName(name: string): ClassWithFactory + static with4Name(name: string): Promise + static with4NameResult(name: string): Promise setName(name: string): this } diff --git a/examples/napi/src/class_factory.rs b/examples/napi/src/class_factory.rs index 50daac52..39116d7c 100644 --- a/examples/napi/src/class_factory.rs +++ b/examples/napi/src/class_factory.rs @@ -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 { + Ok(Self { + name: format!("{name}-{}", always_4().await), + }) + } + #[napi] pub fn set_name(&mut self, name: String) -> &Self { self.name = name;