Back to Promises

Promise Quick Start

docs/promise-quick-start.md

3.0.09.0 KB
Original Source

Promise Quick Start

This guide covers the common promise operations needed when using guzzlehttp/promises directly: registering callbacks, resolving or rejecting promises, waiting synchronously, composing chains, and using generator-based flows. For the full public surface, see the Promise API.

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either the eventual value or the reason why the promise cannot be fulfilled.

Guzzle promise callbacks are queued. They run when the task queue is drained, for example by Utils::queue()->run(), by waiting on a returned promise, or by the default shutdown handler at the end of the PHP process. Examples that show callback output drain the queue explicitly.

Callbacks

Callbacks are registered with the then method by providing an optional $onFulfilled followed by an optional $onRejected function.

php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(
    // $onFulfilled
    function ($value) {
        echo 'The promise was fulfilled.';
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);

Resolving a promise means that you either fulfill a promise with a value or reject a promise with a reason. Callbacks registered with then are invoked only once and in the order in which they were added when the queue is drained.

Resolving a Promise

Promises are fulfilled using the resolve($value = null) method. Calling resolve() without an argument fulfills the promise with null. Resolving a promise with any value other than a GuzzleHttp\Promise\RejectedPromise queues the $onFulfilled callbacks. Resolving with a rejected promise rejects the promise and queues the $onRejected callbacks.

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

$promise = new Promise();
$promise
    ->then(function ($value) {
        return 'Hello, ' . $value;
    })
    ->then(function ($value) {
        echo $value;
    });

$promise->resolve('reader.');
Utils::queue()->run();
// Outputs "Hello, reader."

Promise Forwarding

Promises can be chained one after the other. Each then call returns a new promise. The return value of a callback is forwarded to the next promise in the chain. Returning a promise from a callback makes the next promise wait for that returned promise to settle.

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

$promise = new Promise();
$nextPromise = new Promise();

$promise
    ->then(function ($value) use ($nextPromise) {
        echo $value;

        return $nextPromise;
    })
    ->then(function ($value) {
        echo $value;
    });

$promise->resolve('A');
Utils::queue()->run();
// Outputs "A"

$nextPromise->resolve('B');
Utils::queue()->run();
// Outputs "B"

Promise Rejection

When a promise is rejected, the $onRejected callbacks are invoked with the rejection reason when the queue is drained.

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

$promise = new Promise();
$promise->then(null, function ($reason) {
    echo $reason;
});

$promise->reject('Error!');
Utils::queue()->run();
// Outputs "Error!"

Rejection Forwarding

If an exception is thrown in an $onRejected callback, subsequent $onRejected callbacks receive the thrown exception as the reason.

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

$promise = new Promise();
$promise->then(null, function ($reason) {
    throw new Exception($reason);
})->then(null, function ($reason) {
    assert($reason->getMessage() === 'Error!');
});

$promise->reject('Error!');
Utils::queue()->run();

You can also forward a rejection down the promise chain by returning a GuzzleHttp\Promise\RejectedPromise in either an $onFulfilled or $onRejected callback.

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

$promise = new Promise();
$promise->then(null, function ($reason) {
    return new RejectedPromise($reason);
})->then(null, function ($reason) {
    assert($reason === 'Error!');
});

$promise->reject('Error!');
Utils::queue()->run();

If an exception is not thrown in an $onRejected callback and the callback does not return a rejected promise, downstream $onFulfilled callbacks are invoked using the value returned from the $onRejected callback.

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

$promise = new Promise();
$promise
    ->then(null, function ($reason) {
        return "It's ok";
    })
    ->then(function ($value) {
        assert($value === "It's ok");
    });

$promise->reject('Error!');
Utils::queue()->run();

Synchronous Wait

You can synchronously force promises to complete using a promise's wait method. When creating a promise, you can provide a wait function that is used to synchronously complete the promise. The wait function receives a boolean argument and is expected to resolve or reject the promise. If the wait function does not settle the promise, an exception is thrown.

php
use GuzzleHttp\Promise\Promise;

$promise = new Promise(function (bool $recursive) use (&$promise) {
    $promise->resolve('foo');
});

echo $promise->wait();
// Outputs "foo"

If a throwable is encountered while invoking the wait function of a promise, the promise is rejected with the throwable and the throwable is thrown.

php
$promise = new Promise(function (bool $recursive) use (&$promise) {
    throw new Exception('foo');
});

$promise->wait(); // Throws the exception.

Calling wait on a promise that has been fulfilled will not trigger the wait function. It will simply return the previously resolved value.

php
$promise = new Promise(function (bool $recursive) { die('this is not called!'); });
$promise->resolve('foo');

echo $promise->wait();
// Outputs "foo"

Calling wait on a promise that has been rejected will throw. If the rejection reason is an instance of \Throwable, the reason is thrown. Otherwise, a GuzzleHttp\Promise\RejectionException is thrown and the reason can be obtained by calling getReason() on the exception.

php
$promise = new Promise();
$promise->reject('foo');
$promise->wait();

PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with reason: foo'

Unwrapping a Promise

When synchronously waiting on a promise, you are joining the state of the promise into the current execution: the fulfilled value is returned, or the rejection reason is thrown. This is called "unwrapping" the promise. Waiting on a promise unwraps by default.

You can force a promise to resolve and not unwrap its state by passing false to wait():

php
$promise = new Promise();
$promise->reject('foo');

// This does not throw. It only ensures the promise has been resolved.
$promise->wait(false);

When unwrapping a promise, the resolved value of the promise will be waited on until the unwrapped value is not a promise. This means that if promise A is resolved with promise B, unwrapping promise A returns the value delivered to promise B.

When you do not unwrap the promise, no value is returned.

Inspecting a Promise

Utils::inspect($promise) waits for a promise to settle and returns an array describing its final state. For rejected promises, the reason entry is the actual rejection reason delivered to rejection callbacks.

This means RejectionException and subclasses are not unwrapped by inspect(). For example, cancelled promises inspect with a CancellationException reason.

Generator-Based Async

Coroutine::of() creates a promise from a generator that yields values or promises. The generator resumes each time the yielded value settles, which can make sequential asynchronous flows easier to read.

php
use GuzzleHttp\Promise\Coroutine;
use GuzzleHttp\Promise\FulfilledPromise;

$promise = Coroutine::of(function () {
    $first = yield new FulfilledPromise('A');
    $second = yield new FulfilledPromise($first . 'B');

    yield $second . 'C';
});

echo $promise->wait();
// Outputs "ABC"

If a yielded promise rejects, the rejection is thrown into the generator. Catch it inside the generator to recover, or let it reject the coroutine promise.

Cancellation

You can cancel a promise that has not yet been fulfilled using cancel(). When creating a promise, you can provide an optional cancel function that cancels the underlying operation, such as closing a socket or aborting a query.

php
use GuzzleHttp\Promise\Promise;

$promise = new Promise(null, function () {
    // Cancel the underlying operation.
});

$promise->cancel();

Cancellation rejects the promise with a CancellationException unless the cancel function settles the promise first.