diff --git a/crates/napi/src/error.rs b/crates/napi/src/error.rs index b716cd86..5f701f6a 100644 --- a/crates/napi/src/error.rs +++ b/crates/napi/src/error.rs @@ -81,7 +81,10 @@ impl From for Error { let mut result = std::ptr::null_mut(); let status = unsafe { sys::napi_create_reference(value.0.env, value.0.value, 0, &mut result) }; if status != sys::Status::napi_ok { - return Error::new(Status::from(status), "".to_owned()); + return Error::new( + Status::from(status), + "Create Error reference failed".to_owned(), + ); } Self { status: Status::GenericFailure, diff --git a/crates/napi/src/threadsafe_function.rs b/crates/napi/src/threadsafe_function.rs index 65b8c4c9..85421f35 100644 --- a/crates/napi/src/threadsafe_function.rs +++ b/crates/napi/src/threadsafe_function.rs @@ -178,7 +178,7 @@ enum ThreadsafeFunctionCallVariant { struct ThreadsafeFunctionCallJsBackData { data: T, call_variant: ThreadsafeFunctionCallVariant, - callback: Box Result<()>>, + callback: Box) -> Result<()>>, } /// Communicate with the addon's main thread by invoking a JavaScript function from other threads. @@ -434,7 +434,7 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: JsUnknown| Ok(())), + callback: Box::new(|_d: Result| Ok(())), } }))) .cast(), @@ -463,8 +463,8 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: JsUnknown| { - D::from_napi_value(d.0.env, d.0.value).and_then(cb) + callback: Box::new(move |d: Result| { + d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) }), } }))) @@ -478,7 +478,7 @@ impl ThreadsafeFunction { #[cfg(feature = "tokio_rt")] pub async fn call_async(&self, value: Result) -> Result { - let (sender, receiver) = tokio::sync::oneshot::channel::(); + let (sender, receiver) = tokio::sync::oneshot::channel::>(); self.handle.with_read_aborted(|aborted| { if aborted { @@ -492,12 +492,12 @@ impl ThreadsafeFunction { ThreadsafeFunctionCallJsBackData { data, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: JsUnknown| { - D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { - sender.send(d).map_err(|_| { + callback: Box::new(move |d: Result| { + sender + .send(d.and_then(|d| D::from_napi_value(d.0.env, d.0.value))) + .map_err(|_| { crate::Error::from_reason("Failed to send return value to tokio sender") }) - }) }), } }))) @@ -508,7 +508,13 @@ impl ThreadsafeFunction { })?; receiver .await - .map_err(|err| crate::Error::new(Status::GenericFailure, format!("{}", err))) + .map_err(|_| { + crate::Error::new( + Status::GenericFailure, + "Receive value from threadsafe function sender failed", + ) + }) + .and_then(|ret| ret) } } @@ -527,7 +533,7 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::Direct, - callback: Box::new(|_d: JsUnknown| Ok(())), + callback: Box::new(|_d: Result| Ok(())), })) .cast(), mode.into(), @@ -554,8 +560,8 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: JsUnknown| { - D::from_napi_value(d.0.env, d.0.value).and_then(cb) + callback: Box::new(move |d: Result| { + d.and_then(|d| D::from_napi_value(d.0.env, d.0.value).and_then(cb)) }), })) .cast(), @@ -581,10 +587,12 @@ impl ThreadsafeFunction { Box::into_raw(Box::new(ThreadsafeFunctionCallJsBackData { data: value, call_variant: ThreadsafeFunctionCallVariant::WithCallback, - callback: Box::new(move |d: JsUnknown| { - D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { - sender.send(d).map_err(|_| { - crate::Error::from_reason("Failed to send return value to tokio sender") + callback: Box::new(move |d: Result| { + d.and_then(|d| { + D::from_napi_value(d.0.env, d.0.value).and_then(move |d| { + sender.send(d).map_err(|_| { + crate::Error::from_reason("Failed to send return value to tokio sender") + }) }) }) }), @@ -677,7 +685,7 @@ unsafe extern "C" fn call_js_cb( values.collect() }; let mut return_value = ptr::null_mut(); - let status = match args { + let mut status = match args { Ok(args) => unsafe { sys::napi_call_function( raw_env, @@ -705,11 +713,26 @@ unsafe extern "C" fn call_js_cb( }, }; if let ThreadsafeFunctionCallVariant::WithCallback = call_variant { - if let Err(err) = callback(JsUnknown(crate::Value { - env: raw_env, - value: return_value, - value_type: crate::ValueType::Unknown, - })) { + // throw Error in JavaScript callback + let callback_arg = if status == sys::Status::napi_pending_exception { + let mut exception = ptr::null_mut(); + status = unsafe { sys::napi_get_and_clear_last_exception(raw_env, &mut exception) }; + Err( + JsUnknown(crate::Value { + env: raw_env, + value: exception, + value_type: crate::ValueType::Unknown, + }) + .into(), + ) + } else { + Ok(JsUnknown(crate::Value { + env: raw_env, + value: return_value, + value_type: crate::ValueType::Unknown, + })) + }; + if let Err(err) = callback(callback_arg) { let message = format!( "Failed to convert return value in ThreadsafeFunction callback into Rust value: {}", err @@ -717,7 +740,7 @@ unsafe extern "C" fn call_js_cb( let message_length = message.len(); unsafe { sys::napi_fatal_error( - "threadsafe_function.rs:642\0".as_ptr().cast(), + "threadsafe_function.rs:720\0".as_ptr().cast(), 26, CString::new(message).unwrap().into_raw(), message_length, @@ -769,7 +792,7 @@ unsafe extern "C" fn call_js_cb( }, sys::Status::napi_ok, ); - let error_msg = "Call JavaScript callback failed in thread safe function"; + let error_msg = "Call JavaScript callback failed in threadsafe function"; let mut error_msg_value = ptr::null_mut(); assert_eq!( unsafe { diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md index 499a98b4..f7cfbfd9 100644 --- a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md +++ b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.md @@ -518,6 +518,8 @@ Generated by [AVA](https://avajs.dev). ␊ export function tsfnReturnPromiseTimeout(func: (err: Error | null, value: number) => any): Promise␊ ␊ + export function tsfnThrowFromJs(tsfn: (err: Error | null, value: number) => any): Promise␊ + ␊ export function tsRename(a: { foo: number }): string[]␊ ␊ export interface TsTypeChanged {␊ diff --git a/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap b/examples/napi/__tests__/__snapshots__/typegen.spec.ts.snap index 27a610de..5ac96f15 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 b0dbf3ae..f4f63705 100644 --- a/examples/napi/__tests__/values.spec.ts +++ b/examples/napi/__tests__/values.spec.ts @@ -55,6 +55,7 @@ import { threadsafeFunctionClosureCapture, tsfnCallWithCallback, tsfnAsyncCall, + tsfnThrowFromJs, asyncPlus100, getGlobal, getUndefined, @@ -843,6 +844,19 @@ Napi4Test('async call ThreadsafeFunction', async (t) => { ) }) +test('Throw from ThreadsafeFunction JavaScript callback', async (t) => { + const errMsg = 'ThrowFromJavaScriptRawCallback' + await t.throwsAsync( + () => + tsfnThrowFromJs(() => { + throw new Error(errMsg) + }), + { + message: errMsg, + }, + ) +}) + Napi4Test('accept ThreadsafeFunction', async (t) => { await new Promise((resolve, reject) => { acceptThreadsafeFunction((err, value) => { @@ -923,7 +937,7 @@ Napi4Test('object only from js', (t) => { }) }) -Napi4Test('promise in either', async (t) => { +test('promise in either', async (t) => { t.is(await promiseInEither(1), false) t.is(await promiseInEither(20), true) t.is(await promiseInEither(Promise.resolve(1)), false) diff --git a/examples/napi/index.d.ts b/examples/napi/index.d.ts index 9dc42e10..5c86600a 100644 --- a/examples/napi/index.d.ts +++ b/examples/napi/index.d.ts @@ -508,6 +508,8 @@ export function tsfnReturnPromise(func: (err: Error | null, value: number) => an export function tsfnReturnPromiseTimeout(func: (err: Error | null, value: number) => any): Promise +export function tsfnThrowFromJs(tsfn: (err: Error | null, value: number) => any): Promise + export function tsRename(a: { foo: number }): string[] export interface TsTypeChanged { diff --git a/examples/napi/src/threadsafe_function.rs b/examples/napi/src/threadsafe_function.rs index 7e52c489..0bbeb2c6 100644 --- a/examples/napi/src/threadsafe_function.rs +++ b/examples/napi/src/threadsafe_function.rs @@ -160,3 +160,8 @@ pub async fn tsfn_return_promise_timeout(func: ThreadsafeFunction) -> Resul } } } + +#[napi] +pub async fn tsfn_throw_from_js(tsfn: ThreadsafeFunction) -> napi::Result { + tsfn.call_async::>(Ok(42)).await?.await +}