guzzle/promises · error · dynamic (Create::exceptionFor)
exceptionFor($this->result)
Error message
exceptionFor($this->result)
What it means
Promise::wait(true) synchronously resolves the promise, then returns the fulfillment value. If the promise settled as rejected, the rejection reason is passed through Create::exceptionFor() and thrown. This is how Guzzle Promises surfaces asynchronous failures to synchronous call sites: the rejection reason becomes a normal PHP exception at the wait() call.
Solutions
- Wrap the $promise->wait() call in try/catch and handle the rejection reason exception.
- Attach an ->otherwise() or ->then(null, $onRejected) handler before wait() so rejection is handled in the promise chain instead of thrown.
- Check $promise->getState() === Promise::FULFILLED and call wait(false) or inspect the state to avoid the unwrap throw when you only need settlement.
- Fix the root cause of the rejection (network error, invalid request, upstream failure) rather than only catching it.
Example fix
// before
$value = $promise->wait(); // throws if promise was rejected
// after
try {
$value = $promise->wait();
} catch (\Throwable $e) {
// handle the rejection reason surfaced by wait(true)
$value = null;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before waiting, only unwrap when the promise is settled and fulfilled:
use GuzzleHttp\Promise\Promise;
if ($promise->getState() === Promise::FULFILLED) {
$value = $promise->wait(); // safe: will not throw
} Type guard
// Narrow by settlement state before unwrapping
function fulfilledValue(\GuzzleHttp\Promise\PromiseInterface $p) {
if ($p->getState() === \GuzzleHttp\Promise\Promise::FULFILLED) {
return $p->wait(); // returns value, never throws
}
return null; // pending or rejected - do not unwrap
} Try / catch
try {
$value = $promise->wait();
} catch (\GuzzleHttp\Exception\ConnectException $e) {
// network/connection failure
} catch (\GuzzleHttp\Exception\RequestException $e) {
// HTTP-level failure
} catch (\Throwable $e) {
// any other rejection reason thrown by wait(true)
} Prevention
- Always treat wait() as throwing: wrap it in try/catch wherever you block on a promise.
- Attach ->otherwise() handlers to chains before calling wait() so rejections are handled in-promise.
- Check getState() === FULFILLED before unwrapping when rejection is not expected.
- Pass wait(false) when you only need the promise settled, not its value.
- In tests, assert rejection via caught exceptions from wait() rather than letting them bubble.
When it happens
Trigger: Calling $promise->wait() (or wait(true), the default) on a Promise that has been or becomes rejected - e.g. an inner promise rejected with an exception, a coroutine thrown, or rejection() invoked before wait. The thrown value is whatever the promise was rejected with, normalized via Create::exceptionFor().
Common situations: Blocking on an HTTP request with Guzzle that failed (4xx/5xx mapped to exceptions like ConnectException or BadResponseException); a promise chain where an earlier then() handler threw; calling wait() on a promise rejected because a nested coroutine failed; tests that reject promises and expect wait() to propagate the reason.
Related errors
AI-assisted analysis of guzzle/promises@42118e66a5 (2026-09-14).
Data as JSON: /api/errors/bf8bfa04a2a88201.
Report an issue: GitHub.
Appendix: source
Thrown at src/Promise.php:119
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->then(null, $onRejected);
}
public function wait(bool $unwrap = true)
{
$this->waitIfPending();
if ($this->result instanceof PromiseInterface) {
return $this->result->wait($unwrap);
}
if ($unwrap) {
if ($this->state === self::FULFILLED) {
return $this->result;
}
// It's rejected so "unwrap" and throw an exception.
throw Create::exceptionFor($this->result);
}
return null;
}
public function getState(): string
{
return $this->state;
}
public function cancel(): void
{
if ($this->state !== self::PENDING) {
return;
}
$this->waitFn = $this->waitList = null;
View on GitHub (pinned to 42118e66a5)