Library / SDK
guzzle/promises avatar
guzzle/promises

guzzlehttp/promises: a PHP promise library you can wait on

Promises/A+ library for PHP with synchronous support

7,712 stars128 forksPHPMIT

At a glance

What is it?
Guzzle's promise package implements Promises/A+ chaining with a synchronous wait(), plus cancellation and group helpers. It is for PHP developers who want promise composition without pulling in the full HTTP client.
Who is it for?
Adopt guzzlehttp/promises when you need promise composition or cancellation in PHP and do not want guzzlehttp/guzzle as a dependency, and when you are on PHP 7.4 or newer for the 3.0 line. Do not adopt it expecting a concurrent event loop: without a running task queue, callbacks queued by then() do not fire, and wait() blocks the thread.
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 37 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 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What guzzlehttp/promises is for, and who should install it directly

Most PHP developers meet this package indirectly. The README states that most application developers use it through `guzzlehttp/guzzle` by calling methods such as `requestAsync()`, and that you should install this package directly when you need promise composition without the full HTTP client. That sentence is the whole case for the package. If you already depend on Guzzle, the promises library is in your vendor directory whether you asked for it or not.

The direct audience is narrower: code that has to coordinate several deferred operations, attach success and failure handlers, cancel work that is no longer needed, or combine groups of promises, and that has no reason to carry an HTTP client along. The package is small by design, and the README frames it as a library for asynchronous operations rather than a runtime. There is no event loop in the package itself. The synchronous support named in the description is the part that matters most for ordinary PHP request handling, where blocking until a result arrives is acceptable.

Promise chaining, the task queue, and why resolve() alone does nothing

The mechanism visible in the README's quick start is a promise object with `then()`, `resolve()`, `wait()` and `getState()`. You create a `Promise`, register handlers with `then()`, settle it with `resolve()`, and then run the queue with `Utils::queue()->run()`. That last call is not decoration. Handlers registered through `then()` are queued rather than invoked immediately, so a script that resolves a promise and never runs the queue will not print the fulfilment message.

The README also documents `wait()`, which blocks until the promise completes and returns the value. For code that only needs the result, `wait()` is the shorter path and skips the queue question entirely. Cancellation and helpers for working with groups of promises are named in the README as features, with the details living in docs/promise-api.md and docs/implementation-notes.md rather than in the top-level file.

One behaviour deserves attention before you build on top of it. The README warns that `getState()` describes how a promise was settled, not the eventual outcome. A promise resolved with another promise, directly or by returning one from a `then()` handler, reports `fulfilled` while the inner promise may still be pending, and `wait()` can still throw if the inner promise rejects. The README says to poll the state only on promises settled with plain values, or to call `wait()` first, and points at guzzle/promises#101. Treat `getState()` as a statement about the wrapper, not about the result.

Installing guzzlehttp/promises and running a first promise

Installation is a single Composer command. The README gives it without qualification.

bash
composer require guzzlehttp/promises

After that, the quick start in the README is the smallest program that shows the queue behaviour. It creates a promise, registers a fulfilment handler and a rejection handler, resolves with the string `done`, and then runs the queue.

php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;

$promise = new Promise();

$promise->then(
    function ($value) {
        echo 'Fulfilled: ' . $value;
    },
    function ($reason) {
        echo 'Rejected: ' . $reason;
    }
);

$promise->resolve('done');
Utils::queue()->run();

What you should see is `Fulfilled: done`. If you delete the `Utils::queue()->run();` line, nothing is printed, because the handler is still sitting in the queue. That is the single most common surprise with this package, and it is worth reproducing once on purpose.

When you do not want to manage the queue, the README's alternative is to wait synchronously and take the value.

php
$value = $promise->wait();

For the Guzzle use case, the README shows the same shape applied to an HTTP request: `requestAsync()` returns a `GuzzleHttp\Promise\PromiseInterface`, and calling `wait()` on it yields the response. The version table in the README maps 3.0 to PHP `>=7.4,<8.7`, 2.5 to `>=7.2.5,<8.7` and 1.5 to end of life.

The queue is not an event loop, and wait() blocks the thread

This package gives you the vocabulary of asynchronous programming without the concurrency. There is no reactor, no stream selector, no timer wheel. `Utils::queue()->run()` drains queued callbacks and returns. If you are on a single PHP-FPM worker, `wait()` occupies that worker until the underlying work finishes, exactly as a blocking call would. The gain over a plain function call is composition and cancellation, not throughput on one process.

That distinction decides whether the package is the wrong tool. If your goal is to overlap dozens of HTTP requests inside one request handler, the concurrency comes from Guzzle's curl multi handler, not from this library, and you should be using `guzzlehttp/guzzle` rather than the promises package on its own. If your goal is to express a chain of dependent steps, attach error handling in one place, or abandon work that a client disconnected from, the package fits.

The `getState()` caveat is the second failure mode. Code that polls state to decide whether it is safe to read a value will be wrong whenever a promise was resolved with another promise, because the outer promise reports `fulfilled` while the inner one is still pending and may reject. The README's own instruction is to call `wait()` first or to restrict state polling to promises settled with plain values. A defensive wrapper that always calls `wait()` before inspecting state costs you nothing and removes the class of bug entirely.

guzzlehttp/promises compared with react/promise

The natural alternative in the PHP ecosystem is react/promise. The difference is not in the surface syntax, which is similar enough that porting a chain is mostly mechanical. The difference is the runtime assumption. react/promise is built to sit on top of the ReactPHP event loop, where promises are settled by the loop as I/O completes and nothing blocks. guzzlehttp/promises is built to be usable with or without a loop: you can run its queue yourself with `Utils::queue()->run()`, or you can bypass the queue and block with `wait()`.

That makes guzzlehttp/promises the better fit for ordinary synchronous PHP applications, including frameworks that have no event loop at all, and for library authors who cannot assume one. It makes react/promise the better fit when the rest of your stack is already ReactPHP, because then the loop drives settlement and `wait()` is not part of the picture. Choosing guzzlehttp/promises for a ReactPHP application means you are carrying a queue that the loop does not know about. Choosing react/promise for a plain PHP-FPM application means you are carrying a loop you will not run.

Maintenance, release lines and the MIT licence

The repository is not archived, and the last push was on 2026-08-24. The same date carries release 3.0.2, with 2.5.3 published about forty-eight minutes earlier and 3.0.1 on 2026-08-05. A maintenance release appearing on the older 2.5 line on the same day as the current line is the signal that matters here: the project still backports fixes rather than leaving 2.5 to rot, which is what the README's version table promises when it labels 2.5 as Maintenance and 1.5 as End of Life.

Upgrade cost is concentrated in the major version. The repository ships UPGRADING.md, which is where the 2.x to 3.x changes are listed, and the README's version table tells you the PHP floor moved to `>=7.4` for 3.0. If you are on PHP 7.2 or 7.3, you stay on 2.5. If you are on 1.5, the README marks that line end of life, so the upgrade is not optional in the long run.

The licence is MIT, stated in the README and present as a LICENSE file at the repository root. MIT is permissive: it allows use, modification and redistribution provided the copyright notice and permission notice are retained. That is a description of the licence text, not legal advice, and the obligations you actually carry depend on how you distribute the code. The README also points at a Tidelift subscription for commercial support, and gives [email protected] as the address for vulnerability reports, with a request not to disclose issues publicly before a fix is announced.

Editorial conclusion

Adopt guzzlehttp/promises when you need promise composition or cancellation in PHP and do not want guzzlehttp/guzzle as a dependency, and when you are on PHP 7.4 or newer for the 3.0 line. Do not adopt it expecting a concurrent event loop: without a running task queue, callbacks queued by then() do not fire, and wait() blocks the thread. Before committing, read docs/promise-interoperability.md and docs/implementation-notes.md, and check the getState() caveat tracked in issue 101, because the state of a promise resolved with another promise does not describe the inner promise's outcome.

Frequently asked questions

How do I install guzzlehttp/promises?

Run composer require guzzlehttp/promises. The README states that most application developers get the package through guzzlehttp/guzzle instead, and that you should install it directly when you need promise composition without the full HTTP client.

Why does my guzzlehttp/promises callback not run after I call resolve()?

Handlers registered with then() are queued, so the README's quick start calls Utils::queue()->run() after resolve() to drain them. Without that call the handler stays in the queue and nothing is printed.

What PHP versions does guzzlehttp/promises 3.0 support?

The README's version table lists 3.0 as the latest line supporting PHP >=7.4,<8.7, with 2.5 in maintenance for >=7.2.5,<8.7 and 1.5 end of life.

Does guzzlehttp/promises report the outcome of a promise resolved with another promise?

No. The README states that getState() describes how a promise was settled, not the eventual outcome: a promise resolved with another promise reports fulfilled while the inner promise may still be pending, and wait() can still throw. It advises polling state only on promises settled with plain values, or calling wait() first.

Official sources

  1. guzzle/promises on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/guzzle-promises.svg)](https://hysenlabs.com/projects/guzzle-promises)