Promises/A implementation for PHP.

Overview

Promise

A lightweight implementation of CommonJS Promises/A for PHP.

CI status

The master branch contains the code for the upcoming 3.0 release. For the code of the current stable 2.x release, checkout the 2.x branch.

The upcoming 3.0 release will be the way forward for this package. However we will still actively support 2.0 and 1.0 for those not yet on PHP 7+.

Table of Contents

  1. Introduction
  2. Concepts
  3. API
  4. Examples
  5. Credits
  6. License

Introduction

Promise is a library implementing CommonJS Promises/A for PHP.

It also provides several other useful promise-related concepts, such as joining multiple promises and mapping and reducing collections of promises.

If you've never heard about promises before, read this first.

Concepts

Deferred

A Deferred represents a computation or unit of work that may not have completed yet. Typically (but not always), that computation will be something that executes asynchronously and completes at some point in the future.

Promise

While a deferred represents the computation itself, a Promise represents the result of that computation. Thus, each deferred has a promise that acts as a placeholder for its actual result.

API

Deferred

A deferred represents an operation whose resolution is pending. It has separate promise and resolver parts.

$deferred = new React\Promise\Deferred();

$promise = $deferred->promise();

$deferred->resolve(mixed $value = null);
$deferred->reject(\Throwable $reason);

The promise method returns the promise of the deferred.

The resolve and reject methods control the state of the deferred.

The constructor of the Deferred accepts an optional $canceller argument. See Promise for more information.

Deferred::promise()

$promise = $deferred->promise();

Returns the promise of the deferred, which you can hand out to others while keeping the authority to modify its state to yourself.

Deferred::resolve()

$deferred->resolve(mixed $value = null);

Resolves the promise returned by promise(). All consumers are notified by having $onFulfilled (which they registered via $promise->then()) called with $value.

If $value itself is a promise, the promise will transition to the state of this promise once it is resolved.

Deferred::reject()

$deferred->reject(\Throwable $reason);

Rejects the promise returned by promise(), signalling that the deferred's computation failed. All consumers are notified by having $onRejected (which they registered via $promise->then()) called with $reason.

PromiseInterface

The promise interface provides the common interface for all promise implementations. See Promise for the only public implementation exposed by this package.

A promise represents an eventual outcome, which is either fulfillment (success) and an associated value, or rejection (failure) and an associated reason.

Once in the fulfilled or rejected state, a promise becomes immutable. Neither its state nor its result (or error) can be modified.

PromiseInterface::then()

$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null);

Transforms a promise's value by applying a function to the promise's fulfillment or rejection value. Returns a new promise for the transformed result.

The then() method registers new fulfilled and rejection handlers with a promise (all parameters are optional):

  • $onFulfilled will be invoked once the promise is fulfilled and passed the result as the first argument.
  • $onRejected will be invoked once the promise is rejected and passed the reason as the first argument.

It returns a new promise that will fulfill with the return value of either $onFulfilled or $onRejected, whichever is called, or will reject with the thrown exception if either throws.

A promise makes the following guarantees about handlers registered in the same call to then():

  1. Only one of $onFulfilled or $onRejected will be called, never both.
  2. $onFulfilled and $onRejected will never be called more than once.

See also

PromiseInterface::done()

$promise->done(callable $onFulfilled = null, callable $onRejected = null);

Consumes the promise's ultimate value if the promise fulfills, or handles the ultimate error.

It will cause a fatal error (E_USER_ERROR) if either $onFulfilled or $onRejected throw or return a rejected promise.

Since the purpose of done() is consumption rather than transformation, done() always returns null.

See also

PromiseInterface::otherwise()

$promise->otherwise(callable $onRejected);

Registers a rejection handler for promise. It is a shortcut for:

$promise->then(null, $onRejected);

Additionally, you can type hint the $reason argument of $onRejected to catch only specific errors.

$promise
    ->otherwise(function (\RuntimeException $reason) {
        // Only catch \RuntimeException instances
        // All other types of errors will propagate automatically
    })
    ->otherwise(function (\Throwable $reason) {
        // Catch other errors
    )};

PromiseInterface::always()

$newPromise = $promise->always(callable $onFulfilledOrRejected);

Allows you to execute "cleanup" type tasks in a promise chain.

It arranges for $onFulfilledOrRejected to be called, with no arguments, when the promise is either fulfilled or rejected.

  • If $promise fulfills, and $onFulfilledOrRejected returns successfully, $newPromise will fulfill with the same value as $promise.
  • If $promise fulfills, and $onFulfilledOrRejected throws or returns a rejected promise, $newPromise will reject with the thrown exception or rejected promise's reason.
  • If $promise rejects, and $onFulfilledOrRejected returns successfully, $newPromise will reject with the same reason as $promise.
  • If $promise rejects, and $onFulfilledOrRejected throws or returns a rejected promise, $newPromise will reject with the thrown exception or rejected promise's reason.

always() behaves similarly to the synchronous finally statement. When combined with otherwise(), always() allows you to write code that is similar to the familiar synchronous catch/finally pair.

Consider the following synchronous code:

try {
  return doSomething();
} catch (\Throwable $e) {
    return handleError($e);
} finally {
    cleanup();
}

Similar asynchronous code (with doSomething() that returns a promise) can be written:

return doSomething()
    ->otherwise('handleError')
    ->always('cleanup');

PromiseInterface::cancel()

$promise->cancel();

The cancel() method notifies the creator of the promise that there is no further interest in the results of the operation.

Once a promise is settled (either fulfilled or rejected), calling cancel() on a promise has no effect.

Promise

Creates a promise whose state is controlled by the functions passed to $resolver.

$resolver = function (callable $resolve, callable $reject) {
    // Do some work, possibly asynchronously, and then
    // resolve or reject.

    $resolve($awesomeResult);
    // or throw new Exception('Promise rejected');
    // or $resolve($anotherPromise);
    // or $reject($nastyError);
};

$canceller = function () {
    // Cancel/abort any running operations like network connections, streams etc.

    // Reject promise by throwing an exception
    throw new Exception('Promise cancelled');
};

$promise = new React\Promise\Promise($resolver, $canceller);

The promise constructor receives a resolver function and an optional canceller function which both will be called with 3 arguments:

  • $resolve($value) - Primary function that seals the fate of the returned promise. Accepts either a non-promise value, or another promise. When called with a non-promise value, fulfills promise with that value. When called with another promise, e.g. $resolve($otherPromise), promise's fate will be equivalent to that of $otherPromise.
  • $reject($reason) - Function that rejects the promise. It is recommended to just throw an exception instead of using $reject().

If the resolver or canceller throw an exception, the promise will be rejected with that thrown exception as the rejection reason.

The resolver function will be called immediately, the canceller function only once all consumers called the cancel() method of the promise.

Functions

Useful functions for creating, joining, mapping and reducing collections of promises.

All functions working on promise collections (like all(), race(), some() etc.) support cancellation. This means, if you call cancel() on the returned promise, all promises in the collection are cancelled.

resolve()

$promise = React\Promise\resolve(mixed $promiseOrValue);

Creates a promise for the supplied $promiseOrValue.

If $promiseOrValue is a value, it will be the resolution value of the returned promise.

If $promiseOrValue is a thenable (any object that provides a then() method), a trusted promise that follows the state of the thenable is returned.

If $promiseOrValue is a promise, it will be returned as is.

reject()

$promise = React\Promise\reject(\Throwable $reason);

Creates a rejected promise for the supplied $reason.

Note that the \Throwable interface introduced in PHP 7 covers both user land \Exception's and \Error internal PHP errors. By enforcing \Throwable as reason to reject a promise, any language error or user land exception can be used to reject a promise.

all()

$promise = React\Promise\all(array $promisesOrValues);

Returns a promise that will resolve only once all the items in $promisesOrValues have resolved. The resolution value of the returned promise will be an array containing the resolution values of each of the items in $promisesOrValues.

race()

$promise = React\Promise\race(array $promisesOrValues);

Initiates a competitive race that allows one winner. Returns a promise which is resolved in the same way the first settled promise resolves.

The returned promise will become infinitely pending if $promisesOrValues contains 0 items.

any()

$promise = React\Promise\any(array $promisesOrValues);

Returns a promise that will resolve when any one of the items in $promisesOrValues resolves. The resolution value of the returned promise will be the resolution value of the triggering item.

The returned promise will only reject if all items in $promisesOrValues are rejected. The rejection value will be a React\Promise\Exception\CompositeException which holds all rejection reasons. The rejection reasons can be obtained with CompositeException::getThrowables().

The returned promise will also reject with a React\Promise\Exception\LengthException if $promisesOrValues contains 0 items.

some()

$promise = React\Promise\some(array $promisesOrValues, integer $howMany);

Returns a promise that will resolve when at least $howMany of the supplied items in $promisesOrValues fulfill. The resolution value of the returned promise will be an array of length $howMany containing the resolution values of $howMany fulfilled promises that were resolved first.

The returned promise will reject if it becomes impossible for $howMany items to resolve (that is, when (count($promisesOrValues) - $howMany) + 1 items reject). The rejection value will be a React\Promise\Exception\CompositeException which holds (count($promisesOrValues) - $howMany) + 1 rejection reasons. The rejection reasons can be obtained with CompositeException::getExceptions().

The returned promise will also reject with a React\Promise\Exception\LengthException if $promisesOrValues contains less items than $howMany.

map()

$promise = React\Promise\map(array $promisesOrValues, callable $mapFunc);

Traditional map function, similar to array_map(), but allows input to contain promises and/or values, and $mapFunc may return either a value or a promise.

The map function receives each item as argument, where item is a fully resolved value of a promise or value in $promisesOrValues.

reduce()

$promise = React\Promise\reduce(array $promisesOrValues, callable $reduceFunc, $initialValue = null);

Traditional reduce function, similar to array_reduce(), but input may contain promises and/or values, and $reduceFunc may return either a value or a promise, and $initialValue may be a promise or a value for the starting value.

PromisorInterface

The React\Promise\PromisorInterface provides a common interface for objects that provide a promise. React\Promise\Deferred implements it, but since it is part of the public API anyone can implement it.

Examples

How to use Deferred

function getAwesomeResultPromise()
{
    $deferred = new React\Promise\Deferred();

    // Execute a Node.js-style function using the callback pattern
    computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) {
        if ($error) {
            $deferred->reject($error);
        } else {
            $deferred->resolve($result);
        }
    });

    // Return the promise
    return $deferred->promise();
}

getAwesomeResultPromise()
    ->then(
        function ($value) {
            // Deferred resolved, do something with $value
        },
        function (\Throwable $reason) {
            // Deferred rejected, do something with $reason
        }
    );

How promise forwarding works

A few simple examples to show how the mechanics of Promises/A forwarding works. These examples are contrived, of course, and in real usage, promise chains will typically be spread across several function calls, or even several levels of your application architecture.

Resolution forwarding

Resolved promises forward resolution values to the next promise. The first promise, $deferred->promise(), will resolve with the value passed to $deferred->resolve() below.

Each call to then() returns a new promise that will resolve with the return value of the previous handler. This creates a promise "pipeline".

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        // $x will be the value passed to $deferred->resolve() below
        // and returns a *new promise* for $x + 1
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 2
        // This handler receives the return value of the
        // previous handler.
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 3
        // This handler receives the return value of the
        // previous handler.
        return $x + 1;
    })
    ->then(function ($x) {
        // $x === 4
        // This handler receives the return value of the
        // previous handler.
        echo 'Resolve ' . $x;
    });

$deferred->resolve(1); // Prints "Resolve 4"

Rejection forwarding

Rejected promises behave similarly, and also work similarly to try/catch: When you catch an exception, you must rethrow for it to propagate.

Similarly, when you handle a rejected promise, to propagate the rejection, "rethrow" it by either returning a rejected promise, or actually throwing (since promise translates thrown exceptions into rejections)

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        throw new \Exception($x + 1);
    })
    ->otherwise(function (\Exception $x) {
        // Propagate the rejection
        throw $x;
    })
    ->otherwise(function (\Exception $x) {
        // Can also propagate by returning another rejection
        return React\Promise\reject(
            new \Exception($x->getMessage() + 1)
        );
    })
    ->otherwise(function ($x) {
        echo 'Reject ' . $x->getMessage(); // 3
    });

$deferred->resolve(1);  // Prints "Reject 3"

Mixed resolution and rejection forwarding

Just like try/catch, you can choose to propagate or not. Mixing resolutions and rejections will still forward handler results in a predictable way.

$deferred = new React\Promise\Deferred();

$deferred->promise()
    ->then(function ($x) {
        return $x + 1;
    })
    ->then(function ($x) {
        throw new \Exception($x + 1);
    })
    ->otherwise(function (\Exception $x) {
        // Handle the rejection, and don't propagate.
        // This is like catch without a rethrow
        return $x->getMessage() + 1;
    })
    ->then(function ($x) {
        echo 'Mixed ' . $x; // 4
    });

$deferred->resolve(1);  // Prints "Mixed 4"

done() vs. then()

The golden rule is:

Either return your promise, or call done() on it.

At a first glance, then() and done() seem very similar. However, there are important distinctions.

The intent of then() is to transform a promise's value and to pass or return a new promise for the transformed value along to other parts of your code.

The intent of done() is to consume a promise's value, transferring responsibility for the value to your code.

In addition to transforming a value, then() allows you to recover from, or propagate intermediate errors. Any errors that are not handled will be caught by the promise machinery and used to reject the promise returned by then().

Calling done() transfers all responsibility for errors to your code. If an error (either a thrown exception or returned rejection) escapes the $onFulfilled or $onRejected callbacks you provide to done(), it will cause a fatal error.

function getJsonResult()
{
    return queryApi()
        ->then(
            // Transform API results to an object
            function ($jsonResultString) {
                return json_decode($jsonResultString);
            },
            // Transform API errors to an exception
            function ($jsonErrorString) {
                $object = json_decode($jsonErrorString);
                throw new ApiErrorException($object->errorMessage);
            }
        );
}

// Here we provide no rejection handler. If the promise returned has been
// rejected, the ApiErrorException will be thrown
getJsonResult()
    ->done(
        // Consume transformed object
        function ($jsonResultObject) {
            // Do something with $jsonResultObject
        }
    );

// Here we provide a rejection handler which will either throw while debugging
// or log the exception
getJsonResult()
    ->done(
        function ($jsonResultObject) {
            // Do something with $jsonResultObject
        },
        function (ApiErrorException $exception) {
            if (isDebug()) {
                throw $exception;
            } else {
                logException($exception);
            }
        }
    );

Credits

Promise is a port of when.js by Brian Cavalier.

Also, large parts of the documentation have been ported from the when.js Wiki and the API docs.

License

Released under the MIT license.

Comments
  • Enforce \Exception instances as rejection values

    Enforce \Exception instances as rejection values

    This also removes support for rejecting with a Promise instance.

    Decide whether the rejection value is still an optional argument and can be null. Enforcing an argument is more inline with throw (you can't throw null) and let's us remove UnhandledRejectionException.

    new feature BC break 
    opened by jsor 28
  • Introduce ExtendedPromiseInterface

    Introduce ExtendedPromiseInterface

    The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut and utility methods which are not part of the Promises/A specification.

    Implemented methods:

    ExtendedPromiseInterface::done()

    Status: Implemented

    $promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
    

    When working with promises, it's a common issue that exceptions (and/or rejections) get lost because there is no way to break out of a promise chain.

    With the new ExtendedPromiseInterface::done() you can end a promise chain and unhandled rejections/exceptions will be rethrown in an uncatchable way causing a fatal error.

    See:

    ExtendedPromiseInterface::otherwise()

    Status: Implemented

    $promise->otherwise(callable $onRejected);
    

    Shorthand for $promise->then(null, $onRejected).

    Additionally, you can type hint the $reason argument of $onRejected to catch only specific errors.

    Example:

    $promise
        ->otherwise(function(\RuntimeException $reason) {
            // Only catch \RuntimeException instances
        })
        ->otherwise(function(\Exception $reason) {
            // Only catch \Exception instances
        })
        ->otherwise(function($reason) {
            // Catch other errors
        });
    

    The typehint is checked via reflection from $onRejected. Performance impact?

    See:

    ExtendedPromiseInterface::always()

    Status: Implemented

    $promise->always(callable $onFulfilledOrRejected);
    

    The $onFulfilledOrRejected handler gets called, with no arguments, when the promise either fulfills or rejects.

    Example

    $promise->always(function () {
        // close files, connections, etc.
    });
    

    See:

    ExtendedPromiseInterface::progress()

    Status: Implemented

    $promise->progress(callable $onProgress);
    

    Shorthand for $promise->then(null, null, $onProgress).

    See:


    I would love to hear feeback

    • on the addition of ExtendedPromiseInterface at all
    • on the implementations of the methods of ExtendedPromiseInterface
    new feature 
    opened by jsor 25
  • Enforce throwables/exceptions as rejection reasons

    Enforce throwables/exceptions as rejection reasons

    Because rejections are the promise counter parts of throwing an exception, throwables/exceptions should be enforced as rejection reasons.

    Also, ReactPHP has always used exceptions as rejection reasons throughout it's components.

    Closes #46

    new feature BC break 
    opened by jsor 18
  • Difficulty testing with exception design

    Difficulty testing with exception design

    Contrary to #46 perhaps we shouldn't be catching \Exception in function then() at all.

    Just spent a bit of time debugging why I couldn't unit test promises.

    $promise->then(function($results) {
        $this->assertCount(2, $results);
    });
    

    This was not failing the test when count was not correct. Finally figured out that phpunit is actually throwing an exception when the assert fails (I didn't know this), and promise->then() uses a try/catch around the resolve call (I also didn't know this.)

    I feel like the try/catch from within the then() function probably makes things more difficult than it helps.

    Granted, my workaround is to use(&$rowcount), but it still seems throwing an exception from within a resolve, ought to bounce out to the caller.

    $rowcount = false;
    $promise->then(function($results) use (&$rowcount) {
        $rowcount = count($results);
    });
    $this->assertEquals(2, $rowcount);
    
    question 
    opened by dustingraham 17
  • Support intersection types (PHP 8.1+ / Promise v2)

    Support intersection types (PHP 8.1+ / Promise v2)

    PHP8's reflection API changed to make room for handling of union-types. As one of the consequences ReflectionParameter::getClass got marked as deprecated. This commit moves the legacy path behind a version-check for PHP versions before 8 and adds a new more complex path for PHP8+.

    Fixes #192.

    new feature 
    opened by bzikarsky 16
  • [RFC] Change cancellation semantics

    [RFC] Change cancellation semantics

    This issue serves a basis for discussing changing the cancellation semantics. The change is best described by quoting the bluebird docs:

    The new cancellation has "don't care" semantics while the old cancellation had abort semantics. Cancelling a promise simply means that its handler callbacks will not be called.

    At the moment, there is no predictable behavior when cancelling promises because the producer of the root promise decides what the behaviour is (eg. rejecting the promises).

    Changing the semantics would also allow for internal performance and memory optimizations because registered handlers could be discarded (see #55).

    New Promise constructor and resolver function signature:

    $promise = new Promise(function(callable $resolve, callable $reject, callable $onCancel) {
        $onCancel(function() {
            // Do something on cancellation
        });
    });
    

    Note, that the second $canceller argument has been removed from the Promise constructor. Everything is now handled inside the resolver function. Advantage: Resolution and cancellation share the same scope and multiple cancellation callbacks can be registered via $onCancel. Possible problem: The third $onCancel argument has been the $notify callback in 2.x. This might lead to subtle bugs when upgrading from 2.x to 3.x.

    Handlers

    Handlers registered with always() are called even if the promise is cancelled as it is the finally counterpart from try/catch. No other types of handlers (registered with then(), otherwise() etc.) will be called in case of cancellation.

    Consuming cancelled promises will return a promise that is rejected with a CancellationException as the rejection reason.

    new feature help wanted BC break 
    opened by jsor 14
  • Global queue

    Global queue

    This PR introduces a global task queue to eliminate the problem of deep recursion as discussed in #22.

    As noted in this issue, this also opens the possibility to implement future-turn resolutions (#4) through a event loop based queue implementation (example: https://gist.github.com/jsor/52bde3f82014e3898758 for a React\EventLoop based implementation).

    Closes #22

    new feature 
    opened by jsor 14
  • When serializing & later unserializing exceptions thrown by clashing function calls

    When serializing & later unserializing exceptions thrown by clashing function calls

    If you serialize a Deferred instance, and later restore / unserialize it (using Opis/Closure) - attempting to resolve from there will potentially throw errors if there are other global functions named resolve or reject.

    As is the case with Laravel (where there is a global resolve function).

    1. Create a Deferred instance
    2. Define a then or done.
    3. Serialize the instance
    4. Restore / unserialize it
    5. Resolve the unserialized Deferred instance $unserializedDeferred->resolve('foo');

    This throws an error in Laravel as the namespace seems to be lost when unserializing. Meaning the call to the resolve() function (targeting \React\Promise\resolve) actually calls Laravel's global resolve function - throwing an error.

    By being more specific on the function call and including the namespace, this avoids this confusion & calls the correct method.

    For example: on line 232 of \React\Promise\Promise

    Instead of:

    $target->settle(resolve($value));
    

    Call:

    $target->settle(\React\Promise\resolve($value));
    

    IMO this makes the code easier to read also - removing any ambiguity as to where these functions are defined.

    I've submitted a PR to address the issue: https://github.com/reactphp/promise/pull/179

    question 
    opened by brentkelly 13
  • \React\Promise\all not preserving array key order

    \React\Promise\all not preserving array key order

    Hi I ran into an strange issue and am wondering is this intended behavior?

    Basically I'm expecting that for the included code the output would be:

    .Array
    (
        [0] => 1
        [1] => 2
        [2] => 3
    )
    

    But instead I'm getting the array keys unordered:

    .Array
    (
        [0] => 1
        [2] => 3
        [1] => 2
    )
    

    Which causes problems for example when json_encoding (PHP thinks it should be encoded as an object not an array).

    Code to reproduce:

    $loop = Factory::create();
    
    $testData = [
        new Promise(
            function ($resolve) use ($loop) {
                $loop->nextTick(
                    function () use ($resolve) {
                        $resolve(1);
                    }
                );
            }
        ),
        new Promise(
            function ($resolve) use ($loop) {
                $loop->nextTick(
                    function () use ($resolve, $loop) {
                        \React\Promise\resolve()->then( function() use ($resolve, $loop) {
                            $loop->nextTick(function () use ($resolve) {
                                $resolve(2);
                            });
                        });
                    }
                );
            }
        ),
        new Promise(
            function ($resolve) use ($loop) {
                $loop->nextTick(
                    function () use ($resolve) {
                        $resolve(3);
                    }
                );
            }
        )
    ];
    
    
    \React\Promise\all($testData)->then(function ($result) {
        print_r($result);
    });
    
    $loop->run();
    

    Thanks!

    new feature 
    opened by lordthorzonus 13
  • Getting undefined function

    Getting undefined function

    Hi,

    When using composer to install your lib I get the following error when consuming it. Call to undefined function React\Promise\resolve()

    Any idea what could be causing this?

    question 
    opened by sebbZA 13
  • Feature: new promise function

    Feature: new promise function "settle()"

    I need a promise which returns all successful answers and ignoring (or collecting) rejected promises.

    A bit like some(), like when.settle https://github.com/cujojs/when/blob/master/docs/api.md#whensettle mentioned in #62

    new feature 
    opened by SimonHeimberg 12
  • [1.x] Add basic template annotations

    [1.x] Add basic template annotations

    This adds basic type safety annotations for static analyzers like PHPStan and Psalm. This will cover around 80% of the use cases and a follow-up PR for all supported versions will be proposed later to get it to a 100% of close to a 100%.

    By adding these annotations methods returning a promise can hint their resolving type by adding @return PromiseInterface<bool> when they for example resolve to a boolean. By doing that Psalm and PHPStan will understand that the following bit of code will not become an issue because the method's contract promised a boolean through the promise:

    $promise->then(static function (bool $isEnabled) {});
    

    However, the following will yield errors:

    $promise->then(static function (string $isEnabled) {});
    

    This PR is a requirement for https://github.com/reactphp/async/pull/40

    new feature maintenance 
    opened by WyriHaximus 0
  • [2.x] Add basic template annotations

    [2.x] Add basic template annotations

    This adds basic type safety annotations for static analyzers like PHPStan and Psalm. This will cover around 80% of the use cases and a follow-up PR for all supported versions will be proposed later to get it to a 100% of close to a 100%.

    By adding these annotations methods returning a promise can hint their resolving type by adding @return PromiseInterface<bool> when they for example resolve to a boolean. By doing that Psalm and PHPStan will understand that the following bit of code will not become an issue because the method's contract promised a boolean through the promise:

    $promise->then(static function (bool $isEnabled) {});
    

    However, the following will yield errors:

    $promise->then(static function (string $isEnabled) {});
    

    This PR is a requirement for https://github.com/reactphp/async/pull/40

    new feature maintenance 
    opened by WyriHaximus 4
  • Enforce always passing promise to race function

    Enforce always passing promise to race function

    The race function has always supported passing an empty array of promises/values into it. And then defaulted to the behavior of returning an ever waiting promise. While this makes sense from a mathematical perspective, it doesn't from a developers' perspective.

    To enforce always having to pass in a promise, the first argument is now required, and any following promises are accepted using a variadic function argument.

    BC break 
    opened by WyriHaximus 6
  • [3.x] Add template annotations

    [3.x] Add template annotations

    I was considering adapting this lib for promises though it's currently impossible to be type-safe with it.

    I found this psalm plugin https://github.com/Bocmah/psalm-reactphp-promise-plugin that only includes stubs for psalm. So I was thinking it can be simply ported into this promise lib so it's also supported by phpstan and by any static analysis overall without need for any additional setup.

    new feature help wanted 
    opened by simPod 8
Releases(v2.9.0)
  • v2.9.0(Feb 11, 2022)

    • Feature: Support union types and address deprecation of ReflectionType::getClass() (PHP 8+). (#198 by @cdosoftei and @SimonFrings)

      $promise->otherwise(function (OverflowException|UnderflowException $e) {
          echo 'Error: ' . $e->getMessage() . PHP_EOL;
      });
      
    • Feature: Support intersection types (PHP 8.1+). (#195 by @bzikarsky)

      $promise->otherwise(function (OverflowException&CacheException $e) {
          echo 'Error: ' . $e->getMessage() . PHP_EOL;
      });
      
    • Improve test suite, use GitHub actions for continuous integration (CI), update to PHPUnit 9, and add full core team to the license. (#174, #183, #186, and #201 by @SimonFrings and #211 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v2.8.0(May 12, 2020)

    • Mark FulfilledPromise, RejectedPromise and LazyPromise as deprecated for Promise v2 (and remove for Promise v3). (#143 and #165 by @clue)

      // deprecated
      $fulfilled = new React\Promise\FulfilledPromise($value);
      $rejected = new React\Promise\RejectedPromise($reason);
      
      // recommended alternatives
      $fulfilled = React\Promise\resolve($value);
      $rejected = React\Promise\reject($reason);
      
    • Fix: Fix checking whether cancellable promise is an object and avoid possible warning. (#168 by @smscr and @jsor)

    • Improve documentation and add docblocks to functions and interfaces. (#135 by @CharlotteDunois)

    • Add .gitattributes to exclude dev files from exports. (#154 by @reedy)

    • Improve test suite, run tests on PHP 7.4 and update PHPUnit test setup. (#163 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v2.7.1(Jan 7, 2019)

    • Fix: file_exists warning when resolving with long strings. (#130 by @sbesselsen)
    • Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function. (#133 by @WyriHaximus)
    Source code(tar.gz)
    Source code(zip)
  • v2.7.0(Jun 13, 2018)

  • v2.6.0(Jun 11, 2018)

    • Feature: Significantly improve memory consumption and performance by only passing resolver args to resolver and canceller if callback requires them. Also use static callbacks without binding to promise, clean up canceller function reference when they are no longer needed and hide resolver and canceller references from call stack on PHP 7+. (#113, #115, #116, #117, #118, #119 and #123 by @clue)

      These changes combined mean that rejecting promises with an Exception should no longer cause any internal circular references which could cause some unexpected memory growth in previous versions. By explicitly avoiding and explicitly cleaning up said references, we can avoid relying on PHP's circular garbage collector to kick in which significantly improves performance when rejecting many promises.

    • Mark legacy progress support / notification API as deprecated (#112 by @clue)

    • Recommend rejecting promises by throwing an exception (#114 by @jsor)

    • Improve documentation to properly instantiate LazyPromise (#121 by @holtkamp)

    • Follower cancellation propagation was originally planned for this release but has been reverted for now and is planned for a future release. (#99 by @jsor and #122 by @clue)

    Source code(tar.gz)
    Source code(zip)
  • v2.5.1(Mar 25, 2017)

  • v2.5.0(Dec 22, 2016)

    • Revert automatic cancellation of pending collection promises once the output promise resolves. This was introduced in 42d86b7 (PR #36, released in v2.3.0) and was both unintended and backward incompatible.

      If you need automatic cancellation, you can use something like:

      function allAndCancel(array $promises)
      {
           return \React\Promise\all($promises)
               ->always(function() use ($promises) {
                   foreach ($promises as $promise) {
                       if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
                           $promise->cancel();
                       }
                   }
              });
      }
      
    • all() and map() functions now preserve the order of the array (#77).

    • Fix circular references when resolving a promise with itself (#71).

    Source code(tar.gz)
    Source code(zip)
  • v2.4.1(May 3, 2016)

  • v2.4.0(Mar 31, 2016)

    • Support foreign thenables in resolve(). Any object that provides a then() method is now assimilated to a trusted promise that follows the state of this thenable (#52).
    • Fix some() and any() for input arrays containing not enough items (#34).
    Source code(tar.gz)
    Source code(zip)
  • v2.3.0(Mar 31, 2016)

    • Allow cancellation of promises returned by functions working on promise collections (#36).
    • Handle \Throwable in the same way as \Exception (#51 by @joshdifabio).
    Source code(tar.gz)
    Source code(zip)
  • v1.2.1(Mar 7, 2016)

  • v1.2.0(Feb 27, 2016)

    This release makes the API more compatible with 2.0 while preserving full backward compatibility.

    • Introduce new CancellablePromiseInterface implemented by all promises.
    • Add new .cancel() method (part of the CancellablePromiseInterface).
    Source code(tar.gz)
    Source code(zip)
  • v2.2.2(Feb 26, 2016)

  • v2.2.1(Jul 3, 2015)

  • v1.1.0(Jul 1, 2015)

    This release makes the API more compatible with 2.0 while preserving full backward compatibility.

    • Add React\Promise\Promise class.
    • Move methods of React\Promise\When and React\Promise\Util to functions while keeping the classes as a proxy for BC.
    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Dec 30, 2014)

  • v2.1.0(Oct 15, 2014)

  • v2.0.0(Dec 10, 2013)

    New major release. The goal was to streamline the API and to make it more compliant with other promise libraries and especially with the new upcoming ES6 promises specification.

    • Add standalone Promise class.
    • Add new React\Promise\race() function.
    • BC break: Bump minimum PHP version to PHP 5.4.
    • BC break: Remove ResolverInterface and PromiseInterface from Deferred.
    • BC break: Change signature of PromiseInterface.
    • BC break: Remove When and Util classes and move static methods to functions.
    • BC break: FulfilledPromise and RejectedPromise now throw an exception when initialized with a promise instead of a value/reason.
    • BC break: React\Promise\Deferred::resolve() and React\Promise\Deferred::reject() no longer return a promise.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.4(Jun 12, 2018)

    • Trigger PHP errors when invalid callback is passed.
    • Fully resolve rejection value before calling rejection handler.
    • Add When::lazy() to create lazy promises which will be initialized once a consumer calls the then() method.
    Source code(tar.gz)
    Source code(zip)
  • v1.0.3(Jun 12, 2018)

  • v1.0.2(Jun 12, 2018)

    • Fix bug in When::any() not correctly unwrapping to a single result value
    • $promiseOrValue argument of When::resolve() and When::reject() is now optional
    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(Jun 12, 2018)

  • v1.0.0(Jun 12, 2018)

Owner
ReactPHP
Event-driven, non-blocking I/O with PHP.
ReactPHP
A non-blocking concurrency framework for PHP applications. 🐘

Amp is a non-blocking concurrency framework for PHP. It provides an event loop, promises and streams as a base for asynchronous programming. Promises

Amp 3.8k Dec 3, 2022
Asynchronous coroutines for PHP 7.

Recoil An asynchronous coroutine kernel for PHP 7. composer require recoil/recoil The Recoil project comprises the following packages: recoil/api - T

Recoil 788 Nov 20, 2022
A minimalistic implementation of Promises for PHP

libPromise A minimalistic implementation of Promises for PHP. Installation via DEVirion Install the DEVirion plugin and start your server. This will c

null 8 Sep 27, 2022
Promises/A+ library for PHP with synchronous support

Guzzle Promises Promises/A+ implementation that handles promise chaining and resolution iteratively, allowing for "infinite" promise chaining while ke

Guzzle 7.3k Nov 27, 2022
PHPUnit assertions for testing ReactPHP promises

ReactPHP Promises Testing A library that provides a set of convenient assertions for testing ReactPHP promises. Under the hood uses clue/php-block-rea

Sergey Zhuk 29 Oct 30, 2022
A PHP implementation of the GraphQL specification based on the JavaScript reference implementation

GraphQL This is a PHP implementation of the GraphQL specification based on the JavaScript reference implementation. Related projects DateTime scalar R

Digia 219 Nov 16, 2022
A PHP VM implementation in PHP

PHPPHP A PHP VM implementation written in PHP. This is a basic VM implemented in PHP using the AST generating parser developed by @nikic To see what's

Anthony Ferrara 801 Oct 1, 2022
Retrofit implementation in PHP. A REST client for PHP.

Retrofit PHP Retrofit is a type-safe REST client. It is blatantly stolen from square/retrofit and implemented in PHP. ❗ UPGRADE NOTICE ❗ Version 3 int

null 153 Sep 27, 2022
Zipkin PHP is the official PHP Tracer implementation for Zipkin

Zipkin PHP is the official PHP Tracer implementation for Zipkin, supported by the OpenZipkin community. Installation composer require openz

Open Zipkin 250 Nov 12, 2022
A pure PHP implementation of the MessagePack serialization format / msgpack.org[PHP]

msgpack.php A pure PHP implementation of the MessagePack serialization format. Features Fully compliant with the latest MessagePack specification, inc

Eugene Leonovich 367 Nov 16, 2022
Php-rpc-server - JSON RPC server implementation for PHP.

JSON RPC Server implementation for PHP. The json-rpc is a very simple protocol. You can see this by reading the protocol specification. This library i

null 4 Sep 28, 2022
A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

A pure PHP implementation of the open Language Server Protocol. Provides static code analysis for PHP for any IDE.

Felix Becker 1.1k Nov 26, 2022
Pure PHP implementation of GraphQL Server – Symfony Bundle

Symfony GraphQl Bundle This is a bundle based on the pure PHP GraphQL Server implementation This bundle provides you with: Full compatibility with the

null 285 Dec 1, 2022
Powerful implementation of the Specification pattern in PHP

RulerZ The central idea of Specification is to separate the statement of how to match a candidate, from the candidate object that it is matched agains

KΓ©vin Gomez 863 Nov 20, 2022
A PHP implementation of bower :bird:

Bowerphp An implementation of bower in PHP. https://bowerphp.github.io/ Installation $ composer require beelab/bowerphp Configuration Currently, you c

BeeLab 474 Nov 20, 2022
Hawk β€” A PHP Implementation

Hawk β€” A PHP Implementation Hawk is an HTTP authentication scheme using a message authentication code (MAC) algorithm to provide partial HTTP request

dflydev 68 Mar 16, 2022
GeoJSON implementation for PHP

GeoJson PHP Library This library implements the GeoJSON format specification. The GeoJson namespace includes classes for each data structure defined i

Jeremy Mikola 272 Nov 26, 2022
A data mapper implementation for your persistence model in PHP.

Atlas.Orm Atlas is a data mapper implementation for persistence models (not domain models). As such, Atlas uses the term "record" to indicate that its

null 428 Sep 28, 2022
PHP implementation of Fowler's Money pattern.

Money PHP library to make working with money safer, easier, and fun! "If I had a dime for every time I've seen someone use FLOAT to store currency, I'

Money PHP 4.1k Nov 17, 2022
PHP 7.4 EventStore Implementation

Prooph Event Store Common classes and interface for Prooph Event Store implementations. Installation You can install prooph/event-store via composer b

null 531 Nov 20, 2022