neon-bindings/neon · error

Attempted to settle JsFuture multiple times

Error message

Attempted to settle JsFuture multiple times

What it means

`JsFuture::to_future` installs `then`/`catch` callbacks that each `take()` a shared `Mutex<Option<...>>` to run the continuation exactly once. The `.expect` fires when the state slot is already empty — i.e. the promise's then/catch callbacks were both invoked, which the JavaScript promise spec forbids. This is a defensive invariant check against a spec-violating or foreign promise-like object.

Solutions

  1. Ensure the object passed to to_future is a genuine native Promise, not a hand-rolled thenable
  2. Fix or drop custom thenable/polyfill implementations that settle twice
  3. Validate inputs with `instanceof Promise` before calling to_future in generic APIs

Example fix

Only call `to_future` on real native Promise objects (`cx.promise()` results or values verified as Promise instances); replace or fix any thenable that invokes its callbacks more than once.
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Inside the generated resolve/reject callbacks, `take_state()` panics if `lock.take()` returns None — the callback is invoked a second time (or both resolve and reject fire), which only happens if `self` is not a spec-compliant native Promise.

Common situations: Passing a non-promise 'thenable' with broken semantics (calls then twice) into to_future; misbehaved mocks or polyfills; embedding engines whose promise implementation violates the spec.


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

Appendix: source

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

        let (tx, rx) = oneshot::channel();
        let take_state = {
            // Note: If this becomes a bottleneck, `unsafe` could be used to avoid it.
            // The promise spec guarantees that it will only be used once.
            let state = Arc::new(Mutex::new(Some((f, tx))));

            move || {
                state
                    .lock()
                    .ok()
                    .and_then(|mut lock| lock.take())
                    // This should never happen because `self` is a native `Promise`
                    // and settling multiple times is a violation of the spec.
                    .expect("Attempted to settle JsFuture multiple times")
            }
        };

        let resolve = JsFunction::new(cx, {
            let take_state = take_state.clone();

            move |mut cx| {
                let (f, tx) = take_state();
                let v = cx.argument::<JsValue>(0)?;

View on GitHub (pinned to 38960e4381)