guzzle/promises · error · dynamic (Create::exceptionFor)

exceptionFor($this->reason)

Error message

exceptionFor($this->reason)

What it means

RejectedPromise::wait(true) always throws: the promise is already settled as rejected, so wait() converts its stored rejection reason into an exception via Create::exceptionFor($this->reason) and throws it. Unlike Promise::wait(), no resolution can occur - a RejectedPromise is immutable, so every wait(true) call rethrows the same reason.

Solutions

  1. Wrap the wait() call in try/catch and inspect the caught exception, which is the original rejection reason.
  2. Handle rejection with ->otherwise() or ->then(null, $onRejected) instead of waiting on the rejected promise.
  3. If you only need settlement, call wait(false), which returns null without throwing.
  4. Restructure code so a RejectedPromise is not awaited directly, e.g. recover with ->otherwise(...) to produce a fulfilled promise first.

Example fix

// before
$result = $rejectedPromise->wait(); // always throws the reason

// after
try {
    $rejectedPromise->wait();
} catch (\Throwable $e) {
    // $e is the reason stored in the RejectedPromise
}
// or, without throwing:
$rejectedPromise->wait(false);
Defensive patterns

Strategy: try-catch

Validate before calling

// A RejectedPromise is rejected by construction - check before waiting:
use GuzzleHttp\Promise\RejectedPromise;
if (!$promise instanceof RejectedPromise) {
    $value = $promise->wait();
}

Type guard

// Reject the eager rejected-promise case before unwrapping
function safeWait(\GuzzleHttp\Promise\PromiseInterface $p) {
    if ($p instanceof \GuzzleHttp\Promise\RejectedPromise) {
        return null; // wait(true) would throw; do not unwrap
    }
    return $p->wait();
}

Try / catch

try {
    $rejectedPromise->wait();
} catch (\Throwable $reason) {
    // $reason is exactly the reason the RejectedPromise was constructed with
}

Prevention

When it happens

Trigger: Calling ->wait() on any instance created with new RejectedPromise($reason) (or returned by helpers that reject eagerly), with the default $unwrap = true. Throws immediately regardless of prior state, because the rejection reason is fixed at construction.

Common situations: Test fixtures that seed a RejectedPromise and call wait() to assert the reason surfaces; code paths that short-circuit to a rejected promise (e.g. invalid input checked synchronously before async work) and are later awaited; mixing eager RejectedPromise values into code that assumes wait() returns a value.

Related errors


AI-assisted analysis of guzzle/promises@42118e66a5 (2026-09-14). Data as JSON: /api/errors/f3fc97f2050020c3. Report an issue: GitHub.

Appendix: source

Thrown at src/RejectedPromise.php:93

    }

    /**
     * @template TRejectedValue = never
     * @template TRejectedReason = never
     *
     * @param callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>) $onRejected Invoked when the promise is rejected.
     *
     * @return PromiseInterface<TRejectedValue, TRejectedReason|\Throwable>
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        if ($unwrap) {
            throw Create::exceptionFor($this->reason);
        }

        return null;
    }

    public function getState(): string
    {
        return self::REJECTED;
    }

    public function resolve($value = null): void
    {
        throw new \LogicException('Cannot resolve a rejected promise');
    }

    public function reject($reason): void
    {
        if ($reason !== $this->reason) {

View on GitHub (pinned to 42118e66a5)