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
- Unwrap first: pass $reason->wait() or extract the exception/value the inner promise settles with.
- Use \GuzzleHttp\Promise\reject($promise) style helpers or \GuzzleHttp\Promise\rejection_for($reason) only with non-thenable reasons.
- If the intent is to forward a failure, return/chain the failing promise directly rather than wrapping it.
- 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
- Reasons must be concrete values (typically \Throwable); unwrap promises before using them as reasons.
- When converting failures between promise libraries, chain the failing promise rather than re-wrapping it.
- Type-check unknown reasons with method_exists($r, 'then') before constructing.
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
- You cannot create a FulfilledPromise with a promise.
- Not enough promises to fulfill count
- Cannot resolve a fulfilled promise
- Cannot reject a fulfilled promise
- should never be serialized
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)