reactphp/promise: a Promises/A implementation for PHP that stays out of the event loop
Promises/A implementation for PHP.
At a glance
- What is it?
- reactphp/promise is a small, MIT-licensed library that gives PHP the Deferred and Promise pair, plus resolve, reject, all, race and any. It is for PHP engineers who already have an asynchronous driver and need the result plumbing, not for people looking for a full async runtime.
- Who is it for?
- Adopt reactphp/promise if you already run an event loop or an async client and need a standard way to represent a result that has not arrived yet. Do not adopt it if you expect the library to schedule work for you: the README never claims it runs anything, and the Deferred constructor's canceller is documented as an optional argument with no scheduling guarantees.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 143 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What reactphp/promise actually solves for PHP callers
PHP has no built-in way to say "this value will exist later, and here is what to do when it does." Callbacks can express that, but they nest, and each nesting level needs its own error path. reactphp/promise replaces that pattern with two objects. A Deferred represents the computation, and a Promise represents its eventual result. The README describes the Deferred as "a computation or unit of work that may not have completed yet" and the Promise as "a placeholder for its actual result."
The library targets PHP engineers who are already inside an asynchronous context: an event loop, a non-blocking HTTP client, a stream reader. It is a result container, not a scheduler. Nothing in the README suggests the library starts timers, spawns workers or drives I/O. That division matters, because it means you can drop it into an existing async stack without adopting a whole framework along with it.
The second job it does is aggregation. The README states the library also provides "joining multiple promises and mapping and reducing collections of promises." That is the part many hand-rolled callback systems never get around to, and it is where a promise library earns its place even in a small codebase.
Deferred, PromiseInterface and the forwarding rules
The data flow is deliberately narrow. You construct a Deferred, hand out its promise with `$deferred->promise()`, and keep the resolve and reject methods to yourself. The README frames this as keeping "the authority to modify its state to yourself" while consumers only see the placeholder. When you call `resolve($value)` or `reject($reason)`, every handler registered through `then()` is notified.
`then()` returns a new promise, not the original. That is the mechanism behind chaining: the return value of `$onFulfilled` or `$onRejected` becomes the fulfillment value of the new promise, and a thrown exception rejects it. The README gives two guarantees for handlers registered in the same `then()` call: only one of `$onFulfilled` or `$onRejected` is called, never both, and neither is called more than once. Once a promise settles it is immutable, and the README is explicit that neither state nor result can be changed afterward.
Resolution forwarding is the detail worth reading twice. If `$value` passed to `resolve()` is itself a promise, the outer promise "will transition to the state of this promise once it is resolved." This is what lets you return a promise from inside a `then()` callback without wrapping it by hand. The README devotes a whole Examples subsection to resolution forwarding, rejection forwarding and the mixed case, which is a fair signal that this is where readers get confused.
Installing reactphp/promise and writing a first chain
The README's Install section is short, and the package is distributed on Packagist. The README does not print a Composer command in the excerpt available here, so the exact invocation is best taken from the Packagist page for react/promise. There are no extensions to compile and no configuration file to create.
Once the package is autoloaded, the smallest real use is a Deferred you resolve yourself. This is the shape the README's own Deferred example uses, with the promise handed out separately from the resolver.
$deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
$deferred->resolve(mixed $value);
$deferred->reject(\Throwable $reason);After `resolve()` is called, a handler registered with `then()` receives the value, and the README notes that all consumers are notified by having `$onFulfilled` called with `$value`. Registration order does not matter: a handler attached to the promise still fires when the deferred settles. From there, `catch()` is documented as a shortcut for `then(null, $onRejected)`, and `finally()` runs a cleanup callback with no arguments whether the chain fulfilled or rejected. The README also shows that `catch()` can type hint its reason argument, so a `\RuntimeException` handler catches only that class and lets everything else propagate to a later `catch()`.
Where the library stops: no loop, no scheduling, no rollback
The most important limitation is structural. reactphp/promise gives you the vocabulary for pending results, but it does not produce the pending results. If your code has nothing asynchronous behind it, a Deferred is pure ceremony: you resolve it on the next line and the promise adds a layer without buying concurrency. The README never presents the library as an async runtime, and treating it as one leads to code that looks concurrent and runs sequentially.
Cancellation is the second thin area. The README notes that the Deferred constructor accepts an optional `$canceller` argument and points to the Promise section for more information, but the excerpt available here does not document the semantics of cancelling a chain that has already settled, nor what happens to sibling promises in an `all()` group when one is cancelled. Anyone building timeout logic needs to read that section in the source README rather than assume.
The third gap is operational. There is no documented global hook for unhandled rejections beyond the `set_rejection_handler()` function listed in the table of contents, and the README does not spell out what happens to a rejection that no `catch()` ever observes. In a long-running process that is the failure mode that quietly eats errors.
How reactphp/promise differs from Guzzle promises and Amp
The closest comparison in PHP is the promise implementation shipped with Guzzle. Guzzle's promises are tied to its own task queue: when you resolve a promise, the queue has to be run for handlers to execute, which couples the promise objects to Guzzle's HTTP stack. reactphp/promise is standalone. The README presents it as a CommonJS Promises/A implementation and nothing more, so it can sit under any driver that calls `resolve()` at the right moment. The trade-off is that you supply the thing that calls resolve; Guzzle supplies it for you.
Amp takes the opposite approach. It ships an event loop, coroutines and its own future type as one integrated stack, so you adopt the runtime and the result type together. reactphp/promise is the smaller commitment: a result type with no runtime attached. If you have already chosen an event loop, that is an advantage. If you have not, you will end up choosing one anyway, and the library will not help you decide.
The function helpers are where the API surface differs most from a bare `then()` implementation. The README lists `resolve()`, `reject()`, `all()`, `race()` and `any()`. `all()` waits for every promise in a set, `race()` settles with the first one to settle, and `any()` settles with the first one to fulfill. Those three have different failure semantics, and picking the wrong one is a common source of bugs in fan-out code.
Maintenance, versioning and the MIT licence in practice
The repository is not archived, and the last push was on 2026-05-10. The most recent release listed is v3.3.0 from 2025-08-19, preceded by v3.2.0 in May 2024 and v3.1.0 in November 2023. The cadence is slow and steady rather than dormant, which fits a library whose job is to implement a fixed specification. There is a CHANGELOG.md at the repository root, and the default branch is 3.x, so the major line is visible in the branch name itself.
Upgrade cost is mostly a function of how much of the deprecated surface you use. The README's table of contents strikes through `PromiseInterface::otherwise()` and `PromiseInterface::always()`, which marks them as removed or deprecated in favour of `catch()` and `finally()`. Code migrating from the 2.x era that still calls `otherwise()` will need edits. The package also ships `phpunit.xml.dist` and `phpunit.xml.legacy`, plus a `phpstan.neon.dist`, so static analysis and tests are part of the repository layout, not an afterthought.
The licence is MIT, stated in both the repository metadata and the LICENSE file. MIT is permissive: it allows commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a description of the licence text, not legal advice; if your organisation has a policy on attribution in distributed binaries, check it against the LICENSE file rather than this paragraph.
Editorial conclusion
Adopt reactphp/promise if you already run an event loop or an async client and need a standard way to represent a result that has not arrived yet. Do not adopt it if you expect the library to schedule work for you: the README never claims it runs anything, and the Deferred constructor's canceller is documented as an optional argument with no scheduling guarantees. Before you commit, read the Cancellation section of the README in full, check the CHANGELOG for how the 3.x line handles rejection handlers, and confirm which of the function helpers (resolve, reject, all, race, any) your call sites actually need.
Frequently asked questions
Is reactphp/promise the same as JavaScript promises?
No. It is a PHP implementation of CommonJS Promises/A, so the Deferred and then() concepts carry over, but the API is PHP-specific, with resolve(), reject(), all(), race() and any() as functions and a Deferred class you construct directly.
How do I install reactphp/promise?
The README's Install section points to the package on Packagist as react/promise, and the repository is a Composer package, so it is added through Composer rather than by copying files.
Does reactphp/promise run asynchronous code by itself?
No. The README describes a Deferred as a computation that may not have completed yet, and the library provides the result container, not the thing that completes it. You need an event loop or an async client that calls resolve() or reject() at the right moment.
What is the difference between all(), race() and any() in reactphp/promise?
The README lists all three as function helpers. all() joins a set of promises, race() settles with whichever promise settles first, and any() settles with the first promise to fulfill. They are not interchangeable, and the failure behaviour differs between them.
What licence does reactphp/promise use?
MIT, according to the repository metadata and the LICENSE file at the repository root. That permits commercial use and modification as long as the copyright and permission notices are kept.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/reactphp-promise)