neon-bindings/neon · error

Must settle a `neon::types::JsPromise` with…

Error message

Must settle a `neon::types::JsPromise` with `neon::types::Deferred`

What it means

A `Deferred` created from `JsPromise::new` must eventually call `resolve`/`reject`/`try_catch` to settle its promise. `Deferred::drop` checks `IS_RUNNING` and panics if an unsettled Deferred is dropped while the event loop is still running, because dropping it silently leaks a forever-pending promise. Like Root, it only warns if the thread is already panicking.

Solutions

  1. Guarantee every Deferred is settled exactly once: call `deferred.resolve(cx, value)` or `deferred.reject(cx, err)` on all paths, including error paths.
  2. Store the Deferred with its promise and settle it in a completion/finalize callback rather than dropping it.
  3. On abort/timeout/cancel paths, call `reject` with a cancellation error instead of dropping the Deferred.
  4. Structure settle logic with a guard (e.g. settle inside a scope that runs even on early return) so `?`/panics can't bypass it.

Example fix

// before
let (promise, deferred) = JsPromise::new(cx);
if bad_input {
    return cx.throw_error("bad"); // deferred dropped unsettled -> panic
}

// after
let (promise, deferred) = JsPromise::new(cx);
if bad_input {
    deferred.reject(cx, "bad input")?; // settle before returning
    return Ok(promise);
}
Defensive patterns

Strategy: validation

Validate before calling

// track outstanding deferreds; on shutdown, reject any still pending
const pending = new Set();
function track(d) { pending.add(d); return () => pending.delete(d); }
// before event-loop teardown: assert pending.size === 0

Type guard

fn is_settled(d: &Deferred) -> bool { /* napi-6: a settled Deferred stores None internally */ false } // prefer design: always settle in a guard

Try / catch

let (promise, deferred) = JsPromise::new(cx);
let result = (|| -> NeonResult<_> {
    let v = risky(cx)?;
    deferred.resolve(cx, v)?;
    Ok(())
})();
if result.is_err() { deferred.reject(cx, "failed")?; } // settle on all paths

Prevention

When it happens

Trigger: Dropping a `Deferred` without calling `resolve`, `reject`, or `try_catch` on it — e.g. discarding the Deferred after `JsPromise::new`, early-returning with `?` before settling, or clearing a map of pending Deferreds while the runtime is alive.

Common situations: Async task registries that evict entries without rejecting; error paths in async functions that return before settling; refactors that move the settle calls into a code path never reached; timeout logic that forgets to reject the promise.

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


AI-assisted analysis of neon-bindings/neon@38960e4381 (2026-09-13). Data as JSON: /api/errors/dead35e4a8189dcf. Report an issue: GitHub.

Appendix: source

Thrown at crates/neon/src/types_impl/promise.rs:463

impl Drop for Deferred {
    #[cfg(not(feature = "napi-6"))]
    fn drop(&mut self) {
        // If `None`, the `Deferred` has already been settled
        if self.internal.is_none() {
            return;
        }

        // Destructors are called during stack unwinding, prevent a double
        // panic and instead prefer to leak.
        if std::thread::panicking() {
            eprintln!("Warning: neon::types::JsPromise leaked during a panic");
            return;
        }

        // Only panic if the event loop is still running
        if let Ok(true) = crate::context::internal::IS_RUNNING.try_with(|v| *v.borrow()) {
            panic!("Must settle a `neon::types::JsPromise` with `neon::types::Deferred`");
        }
    }

    #[cfg(feature = "napi-6")]
    fn drop(&mut self) {
        // If `None`, the `Deferred` has already been settled
        if let Some(internal) = self.internal.take() {
            let _ = self.drop_queue.call(DropData::Deferred(internal), None);
        }
    }
}

#[cfg(all(feature = "napi-5", feature = "futures"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))]
/// A type of JavaScript
/// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
/// object that acts as a [`Future`](std::future::Future).
///

View on GitHub (pinned to 38960e4381)