napi-rs/napi-rs · error · GenericFailure
Promise finally callback was called more than once
Error message
Promise finally callback was called more than once
What it means
Same single-shot guard applied to the `finally` callback of raw native promises: the stored `Option<Cb>` FnOnce is taken on the first finally invocation, and a repeat invocation returns this GenericFailure rather than touching freed memory owned by the `napi_wrap` finalizer.
Solutions
- Ensure the finally handler runs at most once with a settled flag
- Replace custom thenables/polyfills with native Promises or a spec-compliant library
- Treat the native finally callback as strictly one-shot; do not attempt to reuse it
Example fix
// before
finallyHandler(); finallyHandler(); // double invocation
// after
let ran = false;
const runFinally = () => { if (!ran) { ran = true; finallyHandler(); } };
promise.then(onOk, onErr).then(runFinally, runFinally); Defensive patterns
Strategy: validation
Validate before calling
// JS: single-shot finally
let ran = false;
const fin = () => { if (!ran) { ran = true; cleanup(); } }; Try / catch
try { await p } finally { cleanupOnce(); } Prevention
- Use ES2018+ native Promise.prototype.finally
- Wrap cleanup in a once-guard when composing handlers
- Avoid custom polyfills lacking settlement guards
When it happens
Trigger: A thenable or promise polyfill that invokes the finally handler more than once (e.g. a `finally` implementation that calls the callback both for fulfillment and rejection, or re-invokes it on re-settlement) attached to a native raw promise.
Common situations: Custom promise implementations without proper settlement guards; thenables that re-run their handlers when re-emitting events; polyfills predating correct finally semantics (ES2018).
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Promise then callback was called more than once
- Promise catch callback was called more than once
- {}
- InvalidArg
- InvalidArg
AI-assisted analysis of napi-rs/napi-rs@39bd1205e4 (2026-09-13).
Data as JSON: /api/errors/478e7b18f1c73eb1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/napi/src/bindgen_runtime/js_values/promise_raw.rs:548
check_status!(
unsafe {
sys::napi_get_cb_info(
env,
cbinfo,
&mut 0,
ptr::null_mut(),
ptr::null_mut(),
&mut rust_cb,
)
},
"Get callback info from finally callback failed"
)?;
// The box is owned by the `napi_wrap` finalizer; only borrow it here and
// `take()` the FnOnce out, so a thenable invoking this callback more than
// once gets an error instead of a use-after-free.
let cb = unsafe { &mut *rust_cb.cast::<Option<Cb>>() };
let Some(cb) = cb.take() else {
return Err(Error::new(
Status::GenericFailure,
"Promise finally callback was called more than once".to_owned(),
));
};
unsafe { U::to_napi_value(env, cb(Env(env))?) }
}
pub struct CallbackContext<T> {
pub env: Env,
pub value: T,
}
impl<T: ToNapiValue> ToNapiValue for CallbackContext<T> {
unsafe fn to_napi_value(env: napi_sys::napi_env, val: Self) -> Result<napi_sys::napi_value> {
T::to_napi_value(env, val.value)
}
}View on GitHub (pinned to 39bd1205e4)