fix(napi): propagation error in function call (#1315)

This commit is contained in:
LongYinan 2022-09-14 19:30:43 +08:00 committed by GitHub
parent 5ba70b0e1a
commit ea18170779
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 79 additions and 8 deletions

View file

@ -343,7 +343,8 @@ impl NapiFn {
let mut ret_ptr = std::ptr::null_mut();
napi::bindgen_prelude::check_status!(
napi::bindgen_prelude::check_pending_exception!(
env,
napi::bindgen_prelude::sys::napi_call_function(
env,
cb.this(),
@ -351,8 +352,7 @@ impl NapiFn {
args.len(),
args.as_ptr(),
&mut ret_ptr
),
"Failed to call napi callback",
)
)?;
#ret

View file

@ -148,6 +148,10 @@ static KNOWN_TYPES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
("Buffer", "Buffer"),
("Vec", "Array<{}>"),
("Result", "Error | {}"),
("Error", "Error"),
("JsError", "Error"),
("JsTypeError", "TypeError"),
("JsRangeError", "RangeError"),
("ClassInstance", "{}"),
("Either", "{} | {}"),
("Either3", "{} | {} | {}"),

View file

@ -12,6 +12,7 @@ use serde::{de, ser};
#[cfg(feature = "serde-json")]
use serde_json::Error as SerdeJSONError;
use crate::bindgen_runtime::ToNapiValue;
use crate::{check_status, sys, Env, JsUnknown, NapiValue, Status};
pub type Result<T> = std::result::Result<T, Error>;
@ -28,6 +29,21 @@ pub struct Error {
maybe_env: sys::napi_env,
}
impl ToNapiValue for Error {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
if val.maybe_raw.is_null() {
let err = unsafe { JsError::from(val).into_value(env) };
Ok(err)
} else {
let mut value = std::ptr::null_mut();
check_status!(unsafe {
sys::napi_get_reference_value(val.maybe_env, val.maybe_raw, &mut value)
})?;
Ok(value)
}
}
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
@ -294,6 +310,12 @@ macro_rules! impl_object_methods {
Self(err)
}
}
impl crate::bindgen_prelude::ToNapiValue for $js_value {
unsafe fn to_napi_value(env: sys::napi_env, val: Self) -> Result<sys::napi_value> {
unsafe { ToNapiValue::to_napi_value(env, val.0) }
}
}
};
}
@ -334,7 +356,8 @@ macro_rules! check_status {
#[doc(hidden)]
#[macro_export]
macro_rules! check_pending_exception {
($env: expr, $code:expr) => {{
($env:expr, $code:expr) => {{
use $crate::NapiValue;
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
@ -344,11 +367,30 @@ macro_rules! check_pending_exception {
unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
$crate::sys::Status::napi_ok
);
return Err(Error::from(unsafe {
JsUnknown::from_raw_unchecked($env, error_result)
return Err($crate::Error::from(unsafe {
$crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
}));
}
_ => Err($crate::Error::new($crate::Status::from(c), "".to_owned())),
}
}};
($env:expr, $code:expr, $($msg:tt)*) => {{
use $crate::NapiValue;
let c = $code;
match c {
$crate::sys::Status::napi_ok => Ok(()),
$crate::sys::Status::napi_pending_exception => {
let mut error_result = std::ptr::null_mut();
assert_eq!(
unsafe { $crate::sys::napi_get_and_clear_last_exception($env, &mut error_result) },
$crate::sys::Status::napi_ok
);
return Err($crate::Error::from(unsafe {
$crate::bindgen_prelude::Unknown::from_raw_unchecked($env, error_result)
}));
}
_ => Err($crate::Error::new($crate::Status::from(c), format!($($msg)*))),
}
}};
}

View file

@ -160,8 +160,9 @@ pub mod bindgen_prelude {
#[cfg(feature = "tokio_rt")]
pub use crate::tokio_runtime::*;
pub use crate::{
assert_type_of, bindgen_runtime::*, check_status, check_status_or_throw, error, error::*, sys,
type_of, JsError, Property, PropertyAttributes, Result, Status, Task, ValueType,
assert_type_of, bindgen_runtime::*, check_pending_exception, check_status,
check_status_or_throw, error, error::*, sys, type_of, JsError, Property, PropertyAttributes,
Result, Status, Task, ValueType,
};
// This function's signature must be kept in sync with the one in tokio_runtime.rs, otherwise napi

View file

@ -43,6 +43,7 @@ Generated by [AVA](https://avajs.dev).
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
export function returnJsFunction(): (...args: any[]) => any␊
export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void␊
export interface ObjectFieldClassInstance {␊
bird: Bird␊
}␊

View file

@ -112,6 +112,7 @@ import {
CustomFinalize,
plusOne,
Width,
captureErrorInCallback,
} from '../'
test('export const', (t) => {
@ -290,6 +291,15 @@ test('callback', (t) => {
t.is(err, undefined)
t.is(content, 'hello world')
})
captureErrorInCallback(
() => {
throw new Error('Testing')
},
(err) => {
t.is((err as Error).message, 'Testing')
},
)
})
test('return function', (t) => {

View file

@ -33,6 +33,7 @@ export function optionOnly(callback: (arg0?: string | undefined | null) => void)
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
export function returnJsFunction(): (...args: any[]) => any
export function callbackReturnPromise<T>(functionInput: () => T | Promise<T>, callback: (err: Error | null, result: T) => void): T | Promise<T>
export function captureErrorInCallback(cb1: () => void, cb2: (arg0: Error) => void): void
export interface ObjectFieldClassInstance {
bird: Bird
}

View file

@ -80,3 +80,15 @@ fn callback_return_promise<T: Fn() -> Result<JsUnknown>>(
Ok(ret)
}
}
#[napi]
pub fn capture_error_in_callback<C: Fn() -> Result<()>, E: Fn(Error) -> Result<()>>(
cb1: C,
cb2: E,
) -> Result<()> {
if let Err(e) = cb1() {
cb2(e)
} else {
Ok(())
}
}