guzzle/promises · error · InvalidArgumentException

You cannot create a RejectedPromise with a promise.

Error message

You cannot create a RejectedPromise with a promise.

What it means

A RejectedPromise represents an already-known rejection reason, so its constructor rejects any object exposing a then() method (a promise or thenable). Nesting a promise as a rejection reason would produce a rejected-promise-of-a-promise, which the library's unwrapping rules cannot represent; the reason must be a concrete value such as an exception.

Solutions

  1. Unwrap first: pass $reason->wait() or extract the exception/value the inner promise settles with.
  2. Use \GuzzleHttp\Promise\reject($promise) style helpers or \GuzzleHttp\Promise\rejection_for($reason) only with non-thenable reasons.
  3. If the intent is to forward a failure, return/chain the failing promise directly rather than wrapping it.
  4. Type-check reasons in your own API and convert promises before constructing.

Example fix

// before
return new RejectedPromise($failedPromise); // InvalidArgumentException
// after
return $failedPromise; // just chain the failing promise
Defensive patterns

Strategy: type-guard

Validate before calling

if (is_object($reason) && method_exists($reason, 'then')) {
    throw new \InvalidArgumentException('Reason must not be a promise/thenable');
}

Type guard

function assertNotThenable($reason): void
{
    if (is_object($reason) && method_exists($reason, 'then')) {
        throw new \InvalidArgumentException('Cannot wrap a promise as a reason; unwrap it first.');
    }
}

Try / catch

try {
    $p = new \GuzzleHttp\Promise\RejectedPromise($reason);
} catch (\InvalidArgumentException $e) {
    return $reason; // just chain the failing promise directly
}

Prevention

When it happens

Trigger: new RejectedPromise($r) where $r is any object with a then() method: another Promise, FulfilledPromise, RejectedPromise, or a foreign thenable.

Common situations: Rejecting with the result of a failed request before unwrapping it; converting errors from another promise library; helper functions that pass reasons through unmodified when the reason is itself a promise; test fixtures double-wrapping reasons.

Related errors


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

Appendix: source

Thrown at src/RejectedPromise.php:31

 * @template TValue = never
 * @template TReason = mixed
 *
 * @implements PromiseInterface<TValue, TReason>
 *
 * @final
 */
class RejectedPromise implements PromiseInterface
{
    /** @var TReason */
    private $reason;

    /**
     * @param TReason $reason
     */
    public function __construct($reason)
    {
        if (is_object($reason) && method_exists($reason, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a RejectedPromise with a promise.'
            );
        }

        $this->reason = $reason;
    }

    /**
     * @template TFulfilledValue = never
     * @template TFulfilledReason = never
     * @template TRejectedValue = never
     * @template TRejectedReason = never
     *
     * @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
     * @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null   $onRejected  Invoked when the promise is rejected.
     *
     * @return ($onRejected is null ? self<TValue, TReason> : PromiseInterface<TRejectedValue, TRejectedReason|\Throwable>)
     */

View on GitHub (pinned to 42118e66a5)