fix(napi): unhandled promise rejection while using EitherN<Promise<..>> (#1452)

This commit is contained in:
LongYinan 2023-01-24 19:07:33 +08:00 committed by GitHub
parent 54dd120880
commit e3adf5dac4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 77 deletions

View file

@ -1,30 +1,9 @@
use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue}; use super::{FromNapiValue, ToNapiValue, TypeName, ValidateNapiValue};
use crate::{ use crate::{
bindgen_runtime::{Null, Undefined, Unknown}, bindgen_runtime::{Null, Undefined, Unknown},
sys, Env, Error, JsUndefined, NapiRaw, NapiValue, Status, ValueType, check_status, sys, Env, Error, JsUndefined, NapiRaw, NapiValue, Status, ValueType,
}; };
#[derive(Debug, Clone, Copy)]
pub enum Either<A, B> {
A(A),
B(B),
}
unsafe impl<A: Send, B: Send> Send for Either<A, B> {}
unsafe impl<A: Sync, B: Sync> Sync for Either<A, B> {}
impl<A: AsRef<T>, B: AsRef<T>, T> AsRef<T> for Either<A, B>
where
T: ?Sized,
{
fn as_ref(&self) -> &T {
match &self {
Self::A(a) => a.as_ref(),
Self::B(b) => b.as_ref(),
}
}
}
impl<A: NapiRaw, B: NapiRaw> Either<A, B> { impl<A: NapiRaw, B: NapiRaw> Either<A, B> {
/// # Safety /// # Safety
/// Backward compatible with `Either` in **v1** /// Backward compatible with `Either` in **v1**
@ -36,25 +15,6 @@ impl<A: NapiRaw, B: NapiRaw> Either<A, B> {
} }
} }
impl<A: ValidateNapiValue, B: ValidateNapiValue> ValidateNapiValue for Either<A, B> {
unsafe fn validate(
env: sys::napi_env,
napi_val: sys::napi_value,
) -> crate::Result<sys::napi_value> {
unsafe { A::validate(env, napi_val).or_else(|_| B::validate(env, napi_val)) }
}
}
impl<A: TypeName, B: TypeName> TypeName for Either<A, B> {
fn type_name() -> &'static str {
"Either"
}
fn value_type() -> ValueType {
ValueType::Unknown
}
}
// Backwards compatibility with v1 // Backwards compatibility with v1
impl<T> From<Either<T, JsUndefined>> for Option<T> { impl<T> From<Either<T, JsUndefined>> for Option<T> {
fn from(value: Either<T, JsUndefined>) -> Option<T> { fn from(value: Either<T, JsUndefined>) -> Option<T> {
@ -83,41 +43,6 @@ impl<T> From<Either<T, Null>> for Option<T> {
} }
} }
impl<
A: TypeName + FromNapiValue + ValidateNapiValue,
B: TypeName + FromNapiValue + ValidateNapiValue,
> FromNapiValue for Either<A, B>
{
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
if unsafe { A::validate(env, napi_val) }.is_ok() {
unsafe { A::from_napi_value(env, napi_val) }.map(Either::A)
} else if unsafe { B::validate(env, napi_val) }.is_ok() {
unsafe { B::from_napi_value(env, napi_val).map(Either::B) }
} else {
Err(Error::new(
Status::InvalidArg,
format!(
"Value is not either {} or {}",
A::type_name(),
B::type_name()
),
))
}
}
}
impl<A: ToNapiValue, B: ToNapiValue> ToNapiValue for Either<A, B> {
unsafe fn to_napi_value(
env: sys::napi_env,
value: Self,
) -> crate::Result<crate::sys::napi_value> {
match value {
Self::A(a) => unsafe { A::to_napi_value(env, a) },
Self::B(b) => unsafe { B::to_napi_value(env, b) },
}
}
}
macro_rules! either_n { macro_rules! either_n {
( $either_name:ident, $( $parameter:ident ),+ $( , )* ) => { ( $either_name:ident, $( $parameter:ident ),+ $( , )* ) => {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -143,7 +68,19 @@ macro_rules! either_n {
unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> { unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> crate::Result<Self> {
let mut ret = Err(Error::new(Status::InvalidArg, "Invalid value".to_owned())); let mut ret = Err(Error::new(Status::InvalidArg, "Invalid value".to_owned()));
$( $(
if unsafe { $parameter::validate(env, napi_val).is_ok() && { ret = $parameter ::from_napi_value(env, napi_val).map(Self:: $parameter ); ret.is_ok() } } { if unsafe {
match $parameter::validate(env, napi_val) {
Ok(maybe_rejected_promise) => {
if maybe_rejected_promise.is_null() {
true
} else {
silence_rejected_promise(env, maybe_rejected_promise)?;
false
}
},
Err(_) => false
}
} && unsafe { { ret = $parameter ::from_napi_value(env, napi_val).map(Self:: $parameter ); ret.is_ok() } } {
ret ret
} else } else
)+ )+
@ -194,6 +131,16 @@ macro_rules! either_n {
} }
} }
impl<Data, $( $parameter: AsRef<Data> ),+ > AsRef<Data> for $either_name < $( $parameter ),+ >
where Data: ?Sized,
{
fn as_ref(&self) -> &Data {
match &self {
$( Self:: $parameter (v) => v.as_ref() ),+
}
}
}
impl< $( $parameter ),+ > $either_name < $( $parameter ),+ > impl< $( $parameter ),+ > $either_name < $( $parameter ),+ >
where $( $parameter: NapiRaw ),+ where $( $parameter: NapiRaw ),+
{ {
@ -206,6 +153,7 @@ macro_rules! either_n {
}; };
} }
either_n!(Either, A, B);
either_n!(Either3, A, B, C); either_n!(Either3, A, B, C);
either_n!(Either4, A, B, C, D); either_n!(Either4, A, B, C, D);
either_n!(Either5, A, B, C, D, E); either_n!(Either5, A, B, C, D, E);
@ -230,3 +178,36 @@ either_n!(Either23, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T,
either_n!(Either24, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X); either_n!(Either24, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
either_n!(Either25, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y); either_n!(Either25, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
either_n!(Either26, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z); either_n!(Either26, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
fn silence_rejected_promise(env: sys::napi_env, promise: sys::napi_value) -> crate::Result<()> {
let mut catch_method = std::ptr::null_mut();
check_status!(unsafe {
sys::napi_get_named_property(env, promise, "catch\0".as_ptr().cast(), &mut catch_method)
})?;
let mut catch_noop_callback = std::ptr::null_mut();
check_status!(unsafe {
sys::napi_create_function(
env,
"catch\0".as_ptr().cast(),
5,
Some(noop),
std::ptr::null_mut(),
&mut catch_noop_callback,
)
})?;
check_status!(unsafe {
sys::napi_call_function(
env,
promise,
catch_method,
1,
vec![catch_noop_callback].as_ptr().cast(),
std::ptr::null_mut(),
)
})?;
Ok(())
}
unsafe extern "C" fn noop(_env: sys::napi_env, _info: sys::napi_callback_info) -> sys::napi_value {
std::ptr::null_mut()
}

View file

@ -82,6 +82,7 @@ Generated by [AVA](https://avajs.dev).
}␊ }␊
export function eitherFromObjects(input: A | B | C): string␊ export function eitherFromObjects(input: A | B | C): string␊
export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void␊ export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void␊
export function promiseInEither(input: number | Promise<number>): Promise<boolean>
/** default enum values are continuos i32s start from 0 */␊ /** default enum values are continuos i32s start from 0 */␊
export const enum Kind {␊ export const enum Kind {␊
/** Barks */␊ /** Barks */␊

View file

@ -119,6 +119,7 @@ import {
bigintFromI64, bigintFromI64,
acceptThreadsafeFunction, acceptThreadsafeFunction,
acceptThreadsafeFunctionFatal, acceptThreadsafeFunctionFatal,
promiseInEither,
} from '../' } from '../'
test('export const', (t) => { test('export const', (t) => {
@ -833,6 +834,15 @@ Napi4Test('object only from js', (t) => {
}) })
}) })
Napi4Test('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)
t.is(await promiseInEither(Promise.resolve(20)), true)
// @ts-expect-error
t.throws(() => promiseInEither('1'))
})
const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip const Napi5Test = Number(process.versions.napi) >= 5 ? test : test.skip
Napi5Test('Date test', (t) => { Napi5Test('Date test', (t) => {

View file

@ -72,6 +72,7 @@ export interface C {
} }
export function eitherFromObjects(input: A | B | C): string export function eitherFromObjects(input: A | B | C): string
export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void export function eitherBoolOrFunction(input: boolean | ((...args: any[]) => any)): void
export function promiseInEither(input: number | Promise<number>): Promise<boolean>
/** default enum values are continuos i32s start from 0 */ /** default enum values are continuos i32s start from 0 */
export const enum Kind { export const enum Kind {
/** Barks */ /** Barks */

View file

@ -130,3 +130,14 @@ pub fn either_from_objects(input: Either3<A, B, C>) -> String {
#[napi] #[napi]
pub fn either_bool_or_function(_input: Either<bool, JsFunction>) {} pub fn either_bool_or_function(_input: Either<bool, JsFunction>) {}
#[napi]
pub async fn promise_in_either(input: Either<u32, Promise<u32>>) -> Result<bool> {
match input {
Either::A(a) => Ok(a > 10),
Either::B(b) => {
let r = b.await?;
Ok(r > 10)
}
}
}