guzzle/promises · error · LogicException

Cannot reject a fulfilled promise

Error message

Cannot reject a fulfilled promise

What it means

A FulfilledPromise is already settled, so it can never be rejected; reject() is unconditional in this implementation and always throws this LogicException. Settled promises are immutable by design - fulfilling and rejecting are mutually exclusive terminal states.

Solutions

  1. Only reject promises that are still pending; track settlement with a boolean or restrict reject() calls to the unsettled branch.
  2. Create a new RejectedPromise (or new Promise + reject()) when you need a rejection outcome.
  3. Route error handling through ->otherwise()/->then(null, $onRejected) on the fulfilled promise rather than trying to mutate it.

Example fix

// before
$fulfilled->reject($reason); // always LogicException
// after
$rejected = new \GuzzleHttp\Promise\RejectedPromise($reason);
Defensive patterns

Strategy: type-guard

Validate before calling

// reject() on a FulfilledPromise always throws; guard by never calling it
// unless you know the concrete class is not settled:
if ($p instanceof \GuzzleHttp\Promise\Promise && $p->getState() === 'pending') {
    $p->reject($reason);
}

Type guard

function canReject(\GuzzleHttp\Promise\PromiseInterface $p): bool
{
    return $p instanceof \GuzzleHttp\Promise\Promise && $p->getState() === 'pending';
}

Try / catch

try {
    $promise->reject($reason);
} catch (\LogicException $e) {
    // already fulfilled; nothing to do
}

Prevention

When it happens

Trigger: Calling ->reject($reason) on any FulfilledPromise instance, no matter the argument. There is no argument value that avoids the throw.

Common situations: Branching code that settles a promise without tracking whether it was already fulfilled; error handlers that reject a promise even after a success path completed; shared promise objects settled from multiple callbacks.

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 guzzle/promises@42118e66a5 (2026-09-14). Data as JSON: /api/errors/351d0a40eed0d5c7. Report an issue: GitHub.

Appendix: source

Thrown at src/FulfilledPromise.php:104

    {
        return $unwrap ? $this->value : null;
    }

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

    public function resolve($value = null): void
    {
        if ($value !== $this->value) {
            throw new \LogicException('Cannot resolve a fulfilled promise');
        }
    }

    public function reject($reason): void
    {
        throw new \LogicException('Cannot reject a fulfilled promise');
    }

    public function cancel(): void
    {
        // pass
    }
}

View on GitHub (pinned to 42118e66a5)