When a JavaScript Error with a .cause property is thrown inside a callee-handled ThreadsafeFunction callback, the resulting napi::Error on the Rust side always has cause: None, discarding the error chain.
Root cause
In call_js_cb in threadsafe_function.rs, when a pending JavaScript exception is detected in the WithCallback branch, Error is constructed directly with cause: None hardcoded:
get_error_message_and_stack_trace(raw_env, exception).and_then(|reason| {
Err(Error {
maybe_raw: error_reference,
maybe_env: raw_env,
cause: None, // never extracted
status: Status::from(raw_status),
reason,
})
})
The From<Unknown> impl in error.rs already knows how to extract .cause from a JavaScript error object:
let maybe_cause: Option<Box<Error>> = value
.coerce_to_object()
.and_then(|obj| obj.get_named_property::<Unknown>("cause"))
.map(|cause| Box::new(cause.into()))
.ok();
The exception path in call_js_cb bypasses this entirely.
Reproduction
JavaScript — throw an error with a cause:
const cause = new Error("root cause");
const err = new Error("wrapper");
err.cause = cause;
throw err;
Rust — cause is always None:
Err(err) => {
println!("{:?}", err.cause); // None — cause is discarded
}
Expected
napi::Error::cause is populated from the thrown JavaScript error's .cause property, consistent with the behavior of From<Unknown>.
Actual
napi::Error::cause is always None, even when the thrown JavaScript exception has a populated .cause property.
Suggested fix
In the WithCallback branch of call_js_cb, extract .cause from the raw exception value using the same approach as From<Unknown> — read get_named_property::<Unknown>("cause") from the exception object and populate the cause field rather than hardcoding None.
Version
napi 3.8.3
When a JavaScript
Errorwith a.causeproperty is thrown inside a callee-handledThreadsafeFunctioncallback, the resultingnapi::Erroron the Rust side always hascause: None, discarding the error chain.Root cause
In
call_js_cbinthreadsafe_function.rs, when a pending JavaScript exception is detected in theWithCallbackbranch,Erroris constructed directly withcause: Nonehardcoded:The
From<Unknown>impl inerror.rsalready knows how to extract.causefrom a JavaScript error object:The exception path in
call_js_cbbypasses this entirely.Reproduction
JavaScript — throw an error with a cause:
Rust —
causeis alwaysNone:Expected
napi::Error::causeis populated from the thrown JavaScript error's.causeproperty, consistent with the behavior ofFrom<Unknown>.Actual
napi::Error::causeis alwaysNone, even when the thrown JavaScript exception has a populated.causeproperty.Suggested fix
In the
WithCallbackbranch ofcall_js_cb, extract.causefrom the raw exception value using the same approach asFrom<Unknown>— readget_named_property::<Unknown>("cause")from the exception object and populate thecausefield rather than hardcodingNone.Version
napi 3.8.3