A Promise/A+ inspired implementation in PHP, with cooperative cancellation support. Inspired by ReactPHP's promise library.
composer require angegroup/promiseRequires PHP 8.2+.
use function promise\resolve;
resolve(42)
->then(fn ($v) => $v * 2)
->then(function ($v) {
echo "Got: $v\n"; // 84
});Construct a promise from a resolver callback. The resolver receives $resolve
and $reject and runs synchronously inside the constructor:
use promise\Promise;
$promise = new Promise(function ($resolve, $reject) {
$resolve('value');
// or $reject(new \RuntimeException('error'));
});Pass an optional second $canceller callable that runs when $promise->cancel()
is called by anyone holding a handle on the promise.
A handle to externally settle a promise — useful when the resolution comes from an event, a timer, or any caller outside the promise constructor:
use promise\Deferred;
$deferred = new Deferred();
// ... later, when something happens:
$deferred->resolve('result');
// or:
$deferred->reject(new \RuntimeException('failure'));
// Hand the promise to consumers:
$deferred->promise()->then(...);All promises implement:
| Method | Purpose |
|---|---|
then(?callable $onFulfilled, ?callable $onRejected) |
Register handlers; returns a derived promise |
catch(callable $onRejected) |
Shorthand for then(null, $onRejected). Type-hint the parameter to filter by exception class |
finally(callable $onFulfilledOrRejected) |
Cleanup callback that runs on either outcome and preserves the original value/reason |
cancel() |
Cooperative cancellation; calls the canceller registered when the promise was created |
wait() |
No-op without an underlying event loop. Kept for interface compatibility |
isResolved() |
True once the promise has settled (either way) |
All in the promise\ namespace.
use function promise\{resolve, reject, all, race, any, allSettled};- A
PromiseInterfaceis returned as-is. - A "thenable" object (any object with a
thenmethod) is adapted into a Promise. - Anything else becomes a
FulfilledPromisecarrying the value.
Always returns a rejected promise carrying the throwable.
Resolves with an array of all values when every input fulfills (preserving keys).
Rejects on the first rejection. Resolves with [] for an empty iterable.
all([resolve(1), resolve(2), 3])->then(fn ($vs) => print_r($vs));
// [1, 2, 3]Settles the same way as the first input that settles (fulfilled or rejected). Pending forever when given an empty iterable.
Resolves with the value of the first input to fulfill. Only rejects if all
inputs reject — the rejection is a CompositeException carrying every reason.
Rejects with LengthException when given an empty iterable.
Resolves once every input has settled. The resolution value is an array of result entries (preserving keys):
[
['status' => 'fulfilled', 'value' => mixed],
['status' => 'rejected', 'reason' => Throwable],
]Never short-circuits. Resolves with [] for an empty iterable.
catch() inspects the rejection handler's first parameter type. If the rejection
reason isn't an instance of that type, the handler is skipped and the rejection
propagates to the next handler:
$promise
->catch(function (NotFoundException $e) {
// only catches NotFoundException
})
->catch(function (\Throwable $e) {
// catches everything else
});Union and intersection types are supported.
| Class | Thrown by |
|---|---|
promise\exception\CompositeException |
any() when every input rejects. getThrowables() returns the per-input reasons. |
promise\exception\LengthException |
any() when given an empty iterable. |
promise\exception\TimeoutException |
Reserved for wait() implementations backed by an event loop. |
composer install
composer test # PHPUnit
composer test-coverage # PHPUnit with coverage summary
composer cs-check # php-cs-fixer dry-run
composer cs-fix # apply formatting
composer phpstan # static analysisCI runs validate, syntax, php-cs-fixer (dry-run), PHPStan level 8 and PHPUnit as five parallel jobs on every push and pull request.
MIT — see LICENSE.