guzzle/promises · error · InvalidArgumentException

You cannot create a FulfilledPromise with a promise.

Error message

You cannot create a FulfilledPromise with a promise.

What it means

A FulfilledPromise represents an already-known fulfilled value, so its constructor rejects any object exposing a then() method (a promise or thenable). Wrapping a promise inside a fulfilled promise would create a promise-of-a-promise, which this library's unwrapping semantics cannot represent. Unwrap the promise to its concrete value first.

Solutions

  1. Resolve the inner promise first: pass $promise->wait() to FulfilledPromise.
  2. Use \GuzzleHttp\Promise\resolve($promise) which unwraps thenables into a settled promise instead of constructing manually.
  3. If the intent was conditional settling, create a new Promise() and call resolve()/reject() on it with the inner outcome.
  4. Type-check inputs before construction and reject promises early in your own API.

Example fix

// before
$wrapped = new FulfilledPromise($innerPromise); // InvalidArgumentException
// after
$wrapped = \GuzzleHttp\Promise\resolve($innerPromise->wait());
Defensive patterns

Strategy: type-guard

Validate before calling

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

Type guard

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

Try / catch

try {
    $p = new \GuzzleHttp\Promise\FulfilledPromise($value);
} catch (\InvalidArgumentException $e) {
    $p = \GuzzleHttp\Promise\resolve($value); // unwraps thenables
}

Prevention

When it happens

Trigger: new FulfilledPromise($x) where $x is any object with a then() method, e.g. another FulfilledPromise, RejectedPromise, Promise, PromiseInterface implementation, or foreign thenable.

Common situations: Passing an unresolved promise into code that expects a plain value; converting from another promise library; double-wrapping results of queue()->run() or coroutines; caching layers that wrap values indiscriminately.

Related errors


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

Appendix: source

Thrown at src/FulfilledPromise.php:31

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

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

        $this->value = $value;
    }

    /**
     * @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 ($onFulfilled is null ? self<TValue, TReason> : PromiseInterface<TFulfilledValue, TFulfilledReason|\Throwable>)
     */

View on GitHub (pinned to 42118e66a5)