guzzlehttp/promises

repository·3.0·Indexed 27 days ago

https://github.com/guzzle/promises

A lightweight PHP promise library for asynchronous operations, providing promise chaining, synchronous waiting, cancellation, and utilities for managing groups of promises. It features iterative resolution for stack safety, support for coroutines via generators, and interoperability with foreign thenables. The library includes specialized classes like FulfilledPromise and RejectedPromise, as well as the Utils class for aggregating promises with methods such as all(), settle(), and any().

Tokens
7.1K
Snippets
22
Records
39
Agent score
93%

What's inside guzzlehttp/promises

  1. Integrate Guzzle promises with an event loop

    3.0

    Guzzle promises use an internal task queue to run callbacks asynchronously. When using promises within an external event loop (like ReactPHP), you must periodically run the Guzzle task queue to prevent callbacks from remaining stuck in the queue.

    To integrate, call run() on the Guzzle task queue during loop ticks. It is recommended to use a periodic timer rather than a zero-interval timer to avoid keeping the loop busy when no promise work is pending.

    $queue = GuzzleHttp\Promise\Utils::queue();
    $loop = React\EventLoop\Factory::create();
    // Use a periodic timer to drain the Guzzle queue
    $loop->addPeriodicTimer(0.01, [$queue, 'run']);
  2. Inspect Rejection Reasons in Guzzle Promises 3.0

    3.0

    In 3.0, Utils::inspect() and Utils::inspectAll() return the actual rejection reason delivered to rejection callbacks. They no longer unwrap RejectionException instances to their inner reason.

    • If a promise is rejected with a RejectionException, inspect() returns that exception as the reason.
    • Cancelled promises inspect with a CancellationException reason.
    • To get the string reason from a RejectionException, call getReason() on the exception.
    use GuzzleHttp// 3.0 behavior
    $reason = new RejectionException('reason');
    $result = Utils::inspect(new RejectedPromise($reason));
    
    assert($result['reason'] === $reason);
  3. Resolve or reject a promise directly

    3.0

    In guzzlehttp/promises, the Promise class acts as both the promise (the representation of a future value) and the deferred object (the mechanism to resolve or reject it). This design allows for efficient, stack-safe iterative resolution.

    Because the Promise is the deferred value, a consumer that holds a Promise instance can call $promise->resolve() or $promise->reject() to deliver a value. To ensure queued callbacks are executed, you may need to run the task queue via GuzzleHttp\Promise\Utils::queue()->run().

    $promise = new Promise();
    $promise->then(function ($value) { echo $value; });
    // The promise is the deferred value, so you can deliver a value to it.
    $promise->resolve('foo');
    GuzzleHttp\Promise\Utils::queue()->run();
    // Prints "foo"
  4. Handle promise rejections and forwarding

    3.0

    When a promise is rejected, $onRejected callbacks are invoked. You can forward rejections in two ways:

    1. Throwing an exception: If an exception is thrown inside an $onRejected callback, subsequent $onRejected callbacks receive that exception.
    2. Returning a RejectedPromise: Returning a GuzzleHttp//Promise//RejectedPromise in either an $onFulfilled or $onRejected callback forwards the rejection down the chain.

    If an $onRejected callback does not throw or return a rejected promise, the chain recovers, and subsequent $onFulfilled callbacks receive the value returned by the rejection handler.

    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();
  5. Use Recursive Mode in Guzzle Promises 3.0 Collection Helpers

    3.0

    In 3.0, Utils::all() and Utils::settle() accept a $recursive flag. When true, helpers continue taking passes over the collection until no new entries are found and no visible promises remain pending. This is intended for rewindable mutable collections like ArrayIterator.

    Note: Generators are safe to pass but cannot be traversed again once consumed, so recursive mode degrades to a single pass.

    use GuzzleHttp// 3.0: Pass the recursive flag before the config array
    $promise = Utils::all($promises, false, ['concurrency' => 5]);
    $promise = Utils::settle($promises, false, ['concurrency' => 5]);
    
    // Using recursive mode
    $promise = Utils::settle($promises, true);
  6. Upgrade from Guzzle Promises 2.x to 3.0

    3.0

    Guzzle Promises 3.0 is a major release with several breaking changes. Key updates include:

    • PHP Version: Requires PHP ^7.4 || ^8.0. If you must support PHP 7.2 or 7.3, stay on 2.x.
    • Dependencies: Removes the runtime dependency on symfony/deprecation-contracts. If your app uses that package, require it directly.
    • Promise Resolution: PromiseInterface::resolve() now accepts an optional value. Calling it without arguments fulfills the promise with null. Custom implementations must update signatures to resolve($value = null): void.
    • Serialization: Promise, TaskQueue, EachPromise, and Coroutine no longer support native PHP serialize() or unserialize(). Persist application values instead of promise runtime state.
    • Helper Classes: Static helper classes (Create, Each, Is, Utils) now have private constructors. Use static methods instead of attempting to instantiate them.
  7. Handle foreign promises in Guzzle

    3.0

    Guzzle can interact with foreign promise implementations (any object with a then method, such as React promises).

    If a foreign promise is returned from a then() callback, Guzzle forwards resolution to that promise. The Guzzle promise returned by the then() call will only settle once the foreign promise invokes its registered callbacks.

    Limitations:

    • Guzzle cannot synchronously wait on or cancel foreign operations unless the foreign object implements compatible wait() or cancel() methods.
    • Forwarding does not automatically grant Guzzle control over the foreign operation's lifecycle.
    use GuzzleHttp\Promise\Promise;
    use GuzzleHttp\Promise\Utils;
    
    $deferred = new React\Promise\Deferred();
    $reactPromise = $deferred->promise();
    
    $guzzlePromise = new Promise();
    $chained = $guzzlePromise->then(function ($value) use ($reactPromise) {
        // Use the Guzzle value, then continue with the React promise.
        return $reactPromise;
    });
    
    $chained->then(function ($value) {
        echo $value;
    });
    
    $guzzlePromise->resolve('start');
    Utils::queue()->run();
    
    $deferred->resolve('done');
    Utils::queue()->run();
  8. Understand iterative resolution and stack safety in promise chains

    3.0

    Guzzle Promises use iterative resolution to maintain a constant stack size, even when dealing with very long .then() chains. Instead of using recursion to deliver values down a chain, the library moves pending handlers between promises.

    • When a promise is fulfilled/rejected with a non-promise value, it takes ownership of child handlers and delivers values without recursion.
    • When a promise is resolved with another promise, the original promise transfers its pending handlers to the new promise, which then forwards the value once resolved.
    <?php
    require 'vendor/autoload.php';
    
    use GuzzleHttp\Promise\Promise;
    
    $parent = new Promise();
    $p = $parent;
    
    for ($i = 0; $i < 1000; $i++) {
        $p = $p->then(function ($v) {
            // The stack size remains constant.
            echo xdebug_get_stack_depth() . ', ';
            return $v + 1;
        });
    }
    
    $parent->resolve(0);
    var_dump($p->wait()); // int(1000)
  9. Upgrade from Guzzle Promises 1.x to 2.0

    3.0

    Guzzle Promises 2.0 is a major release that introduces PHP 7 type hints and removes the function-based API.

    • PHP Version: Requires PHP ^7.2.5 || ^8.0.
    • Type Hints: Method signatures for PromiseInterface, PromisorInterface, and TaskQueueInterface have been updated. Ensure your implementations match these signatures.
    • Final Classes: Most non-exception classes are now final. If you extend them, switch to composition or implement the relevant interface.
    • Function API Removal: The namespaced function API (e.g., promise_for()) has been removed. You must use the corresponding static methods in the GuzzleHttp// namespace.
  10. Update Collection Helper Inputs for Guzzle Promises 3.0

    3.0

    In 3.0, promise collection helpers require iterable inputs. Passing a single promise or a scalar value directly will throw a TypeError. You must wrap single promises or values in an array before passing them to:

    • Create::iterFor()
    • Each::of()
    • Each::ofLimit()
    • Each::ofLimitAll()
    • EachPromise
    • Utils collection helpers

    Additionally, IteratorAggregate inputs are now iterated via getIterator(). In 2.x, an aggregate was treated as a single value; in 3.0, its entries are consumed individually.

    use GuzzleHttp// 2.x
    $promise = Each::ofLimit($singlePromise, 2);
    
    // 3.0
    $promise = Each::ofLimit([$singlePromise], 2);
  11. Quick Start with Guzzle Promises

    3.0

    You can create a new Promise instance, attach success and failure handlers using .then(), and resolve the promise. Note that you must run the task queue via Utils::queue()->run() for the handlers to execute. To wait for a promise to complete synchronously, use the .wait() method.

    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();
    
    // To wait synchronously:
    $value = $promise->wait();