Giter Club home page Giter Club logo

promise's Introduction

Promise

A lightweight implementation of CommonJS Promises/A for PHP.

CI status installs on Packagist

Table of Contents

  1. Introduction
  2. Concepts
  3. API
  4. Examples
  5. Install
  6. Tests
  7. Credits
  8. 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);
$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);

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.

See also the resolve() function.

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.

See also the reject() function.

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::catch()

$promise->catch(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
    ->catch(function (\RuntimeException $reason) {
        // Only catch \RuntimeException instances
        // All other types of errors will propagate automatically
    })
    ->catch(function (\Throwable $reason) {
        // Catch other errors
    });

PromiseInterface::finally()

$newPromise = $promise->finally(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.

finally() behaves similarly to the synchronous finally statement. When combined with catch(), finally() 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()
    ->catch('handleError')
    ->finally('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.

PromiseInterface::otherwise()

Deprecated since v3.0.0, see catch() instead.

The otherwise() method registers a rejection handler for a promise.

This method continues to exist only for BC reasons and to ease upgrading between versions. It is an alias for:

$promise->catch($onRejected);

PromiseInterface::always()

Deprecated since v3.0.0, see finally() instead.

The always() method allows you to execute "cleanup" type tasks in a promise chain.

This method continues to exist only for BC reasons and to ease upgrading between versions. It is an alias for:

$promise->finally($onFulfilledOrRejected);

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 two 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 and joining collections of promises.

All functions working on promise collections (like all(), race(), 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.

The resulting $promise implements the PromiseInterface and can be consumed like any other promise:

$promise = React\Promise\resolve(42);

$promise->then(function (int $result): void {
    var_dump($result);
}, function (\Throwable $e): void {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

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.

The resulting $promise implements the PromiseInterface and can be consumed like any other promise:

$promise = React\Promise\reject(new RuntimeException('Request failed'));

$promise->then(function (int $result): void {
    var_dump($result);
}, function (\Throwable $e): void {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

Note that rejected promises should always be handled similar to how any exceptions should always be caught in a try + catch block. If you remove the last reference to a rejected promise that has not been handled, it will report an unhandled promise rejection:

function incorrect(): int
{
     $promise = React\Promise\reject(new RuntimeException('Request failed'));

     // Commented out: No rejection handler registered here.
     // $promise->then(null, function (\Throwable $e): void { /* ignore */ });

     // Returning from a function will remove all local variable references, hence why
     // this will report an unhandled promise rejection here.
     return 42;
}

// Calling this function will log an error message plus its stack trace:
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
incorrect();

A rejected promise will be considered "handled" if you catch the rejection reason with either the then() method, the catch() method, or the finally() method. Note that each of these methods return a new promise that may again be rejected if you re-throw an exception.

A rejected promise will also be considered "handled" if you abort the operation with the cancel() method (which in turn would usually reject the promise if it is still pending).

See also the set_rejection_handler() function.

all()

$promise = React\Promise\all(iterable $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(iterable $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(iterable $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.

set_rejection_handler()

React\Promise\set_rejection_handler(?callable $callback): ?callable;

Sets the global rejection handler for unhandled promise rejections.

Note that rejected promises should always be handled similar to how any exceptions should always be caught in a try + catch block. If you remove the last reference to a rejected promise that has not been handled, it will report an unhandled promise rejection. See also the reject() function for more details.

The ?callable $callback argument MUST be a valid callback function that accepts a single Throwable argument or a null value to restore the default promise rejection handler. The return value of the callback function will be ignored and has no effect, so you SHOULD return a void value. The callback function MUST NOT throw or the program will be terminated with a fatal error.

The function returns the previous rejection handler or null if using the default promise rejection handler.

The default promise rejection handler will log an error message plus its stack trace:

// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
React\Promise\reject(new RuntimeException('Unhandled'));

The promise rejection handler may be used to use customize the log message or write to custom log targets. As a rule of thumb, this function should only be used as a last resort and promise rejections are best handled with either the then() method, the catch() method, or the finally() method. See also the reject() function for more details.

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);
    })
    ->catch(function (\Exception $x) {
        // Propagate the rejection
        throw $x;
    })
    ->catch(function (\Exception $x) {
        // Can also propagate by returning another rejection
        return React\Promise\reject(
            new \Exception($x->getMessage() + 1)
        );
    })
    ->catch(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);
    })
    ->catch(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"

Install

The recommended way to install this library is through Composer. New to Composer?

This project follows SemVer. This will install the latest supported version from this branch:

composer require react/promise:^3.1

See also the CHANGELOG for details about version upgrades.

This project aims to run on any platform and thus does not require any PHP extensions and supports running on PHP 7.1 through current PHP 8+. It's highly recommended to use the latest supported PHP version for this project.

We're committed to providing long-term support (LTS) options and to provide a smooth upgrade path. If you're using an older PHP version, you may use the 2.x branch (PHP 5.4+) or 1.x branch (PHP 5.3+) which both provide a compatible API but do not take advantage of newer language features. You may target multiple versions at the same time to support a wider range of PHP versions like this:

composer require "react/promise:^3 || ^2 || ^1"

Tests

To run the test suite, you first need to clone this repo and then install all dependencies through Composer:

composer install

To run the test suite, go to the project root and run:

vendor/bin/phpunit

On top of this, we use PHPStan on max level to ensure type safety across the project:

vendor/bin/phpstan

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.

promise's People

Contributors

bzikarsky avatar carusogabriel avatar cboden avatar cdosoftei avatar clue avatar danielecr avatar igorw avatar joshdifabio avatar jsor avatar kubo2 avatar miguilimzero avatar mtdowling avatar nawarian avatar nhedger avatar ondrejmirtes avatar reedy avatar s-bronstein avatar seregazhuk avatar simonfrings avatar sqko avatar woodongwong avatar wyrihaximus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

promise's Issues

Merge interfaces

Merge PromiseInterface, ExtendedPromiseInterface and CancellablePromiseInterface into a single interface (PromiseInterface).

Cannot redeclare React\Promise\resolve()

Hi,

we stumbled over this issue today:

PHP Fatal error:  Cannot redeclare React\Promise\resolve() (previously declared in /root/.composer/vendor/react/promise/src/functions.php:5) in /app/vendor/react/promise/src/React/Promise/functions.php on line 12
PHP Fatal error:  Cannot redeclare React\Promise\resolve() (previously declared in /root/.composer/vendor/react/promise/src/functions.php:5) in /app/vendor/react/promise/src/React/Promise/functions.php on line 12
[...]

We have a composer packages installed globally and per project which both depend on react/promise.

I think the composer autoloader always loads the file specified here which looks to me like the source of the error.

I don't know if composer should handle that...

You could wrap the definitions in a function_exists() call if you have to use the legacy files autoloading.

guzzle/promises

How can I convert guzzle/promises to reactphp/promise ?
As there is no done() method for guzzle/promises , I want to convert it to reactphp/promise

// $guzzlePromise is an instance of GuzzleHttp\Promise\Promise and is a result of third party library
$reactPromise = convert($guzzlePromise);

Validate array argument for promise-array related functions

Reject all() / race() / any() / some() / map() / reduce() when called with a non-array.

all(null)
    ->otherwise(function(\InvalidArgumentException $e) {
        assertEquals('Expecting an array or a promise which resolves to an array, got NULL instead.' , $exception->getMessage());
    });

exception in promise

How can l throw an uncaught exception onRejected?
I try then and done but no difference.

$promise->then(null, function (){
    throw new Exception;
});
$promise->done(null, function (){
    throw new Exception;
});

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.

Creating Promise out of an event emitter ?

Not sure if this is the right project for this, as this depends of both Promise and Evenement.

Having heavily worked with reactphp recently, I ended up creating Promises out of EventEmitters a lot. The typical piece of code looks like this:

$deferred = new Deferred();

$emitter->once('data', [$deferred, 'resolve']);
$emitter->once('error', [$deferred, 'reject']);

return $deferred->promise()
  ->always(function() use ($deferred, $emitter) {
    // Remove listener to avoid leaking memory
    $emitter->removeListener('data', [$deferred, 'resolve']);
    $emitter->removeListener('error', [$deferred, 'reject']);
  });

And this doesn't even handle cancellations.

Wouldn't some kind of EventListenerPromise be useful in the Promise library ?

Catch \Throwable instead of \Exception in PHP7

Type errors are not caught by the React Promise implementation in PHP7. It's fairly trivial to implement catch-all for PHP7 and PHP5 without breaking BC.

try {
  // call a success/error/progress handler
} catch (\Throwable $e) {
  // handle $e
} catch (\Exception $e) {
  // handle $e
}

pthread compatibility

Hi,

I was trying to use react/promise with pthread and started a ticket in the pthread repo,
krakjoe/pthreads#283

Can you confirm if pthread support for react is being worked on?

Thanks,
Shawn

[RFC] Deprecate promise progress API

This ticket serves as a basis for discussing deprecating the promise progress API.
This has come up quite a few times.

As a first step, this ticket aims to only deprecate the existing functionality and update the documentation accordingly. At a later stage, we should consider dropping the functionality altogether (BC break ahead).

Opening this ticket as an RFC and for the reference.

Waiting for all promises to end

Hey, I have an array of promises.

I want to proceed only after all of the promises gave me a response no matter if they were resolved or reject. I thought that all() function can handle it, but it looks like it works only when all of the promises in the array are resolved and without considering rejections for some of the promises.

What function can I use??

example: the function getUser returns a promise object. When all of the promises gave me a response, i would like to catch the trigger, whether the promise is resolved or rejected.

 array_push($this->users['users'], $this->userFetcher->getUser($userName));

Thanks :)

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?

[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.

React\Promise\FulfilledPromise Object

Hi,

I create a promise like this:

public function callService($condition) {
        $deferred = new \React\Promise\Deferred();

        $this->callApi($condition, function($result) use ($deferred) {
            if (empty($result)) {
                $deferred->reject(new \Exception('No prices available!'));
            } else {
                $deferred->resolve($result);
            }
        });

        return $deferred->promise();
}

And in the return of the promise:

return $this->callService($condition)
    ->then(
        function($prices) {   
                return $prices;
        },
        function(\Exception $e) {
                return $e->getMessage();
        }
    );

It return a FulfilledPromise with private value. How can I return only the value of prices?

Thanks for advance.

Resolve before returning?

Here is a contrived example of a promise that is resolved before being used.

function do_something() {
    $deferred = new Deferred();
    $deferred->resolve(true);
    return $deferred->promise();
}

do_something()->then(function() {
    // This is never called, because the resolution has occurred before the call to then.
});

Can anyone suggest a way of using this code in the current release? If not, does anyone have any suggestions to the implementation? My thoughts are to check if the promise is resolved within the then call, and if so, call the onFulfilled function automatically.

Syntax error

Parse error: syntax error, unexpected '[' in /home/seewhaty/public_html/site/vendor/react/promise/src/functions.php on line 75

How am I supposed to deal with the last exception?

I have a code like this:

$promise->then(function() {
    // ...
    throw new FirstException();

})->then(null, function ($error) {
    // ...
    throw new SecondException();

});

SecondException() is lost, never handled.
But say for example that I have a PHP error handler that triggers exceptions.
Say then that I am in the middle of my work on the error handling closure (the second one above).
While I code it, I make mistakes, like usual.
How am I supposed to get the SecondException to my eyes so that I can have a great debugging experience? Adding a third "robust" ->then(null, 'debugProvedHandler')?
I'd prefer having an exception thrown from the last error handler be re-thrown, don't you?
Or any other way I'm missing?

oop(s)

I don't understand the point of having namespaces and oop if we need to require a file having functions defined globally like in functions.php

<?php

if (!function_exists('React\Promise\resolve')) {
    require __DIR__.'/functions.php';
}

Callback wrapper ?

Hello, i writing code using reactphp promise library. I need to call some method and if promise failure then call this method again and again.

First time i written next code:

$instance = new SomeObject();
$onSuccess = function(){
    // do some useful things
};
$onFailure = function() use($instance, $onSuccess, &$onFailure){
    // retry function call
    $instance->method()->then($onSuccess, $onFailure);
};
$instance->method()->then($onSuccess, $onFailure);

This code have 2 problems:

  1. "$instance->method()->then()" is copypaste. When method has params i need to copypaste more and pass through "use" too much params..
  2. If use this way, then i can write buggy code if accidentally reuse $onFailure because it passed via reference.

To avoid this issues i had re-written my code:

class CallbackWrapper{
    public $invoke;
    public $onSuccess;
    public $onFailure;

    public function invoke(){
        call_user_func($this->invoke);
    }
}

$instance = new SomeObject();

$callback = new CallbackWrapper();
$callback->onSuccess = function(){
    // do some useful things
};
$callback->onFailure = function() use($callback){
    $callback->invoke();
};
$callback->invoke = function() use($instance, $callback){
    $instance->method()->then($callback->onSuccess, $callback->onFailure);
};
$callback->invoke();

I see no issues with this code, but have a practical trouble - i need this helper in the majority of projects that uses promises. So maybe it makes sense to include such a helper to the promise library? Or may be i miss something and promise library has another ways to do it?

Sorry for my clumsy English.

Queue broken when throwing from `done()` (dev-master only)

In case there's something enqueued that throws, the queue is never drained again if it wasn't empty after throwing.

<?php

require __DIR__ . "/vendor/autoload.php";

use React\Promise\FulfilledPromise;
use React\Promise\RejectedPromise;

$promiseA = new RejectedPromise(new RuntimeException("A"));

try {
    $promiseA->done(null, function ($e) {
        $promiseB = new RejectedPromise(new RuntimeException("B"));
        $promiseB->then(null, function () {
            print "OK" . PHP_EOL;
        });

        throw $e;
    });
} catch (Throwable $e) {
    // exception caught
}

$promiseC = new FulfilledPromise("C");
$promiseC->then(function () {
    // never executed

    print "C" . PHP_EOL;
});

print " -- END -- " . PHP_EOL;

It's a specially crafted example, not sure whether it will ever happen accidentally. #97 mostly solves this, but should probably also trigger fatal errors thrown from callbacks called in Queue::drain().

\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!

Guarantee future-turn resolutions

At the moment, we're not guaranteeing future-turn resolutions (which basically means, that callbacks should always be called asynchronously, read this).

All major JavaScript libs guarantee that (when.js does not at the moment, but will in their next major release).

We have 2 options.

  1. We do not guarantee future-turn resolutions

    We keep it simple. Methods in When can stay static and creating Deferred's is as simple as doing $d = new Deferred. We should note that in the docs, so that developers are aware that there is no guarantee about if a promise will resolve asynchronously or synchronously.

  2. We do guarantee future-turn resolutions

    This would couple React/Promise to the EventLoop (or at least to some kind of QueueProcessor for which we have an EventLoop adapter). We would have to make When non-static (and require the EventLoop or a QueueProcessor in the constructor which will be passed to Deferredinstances) and it will most probably become some kind of global entry point for creating promises and deferreds.

Synchrony of promises

Hello everyone,
to begin with I am big fan of React project in general, its very promising, congratulation guys! I am sorry for writing a question here, but since this project is hard to find in google at all, waiting for answer in stackoverflow or similar service would take me forever.
I recently discovered promises and was very glad that someone implemented them in PHP since this is a feature I need in my recent project. However I can't understand one thing about react implementation of them and I hope you can help me with that. I read some older issues and have seen that you guys don't want to incorporate ReactLoop into promises, but I can't understand why. Since PHP by default is synchronous and one-threaded, using promises without loop results in synchronous execution of all callbacks in quite atomic-way which negates purpose of promises and is against Promise/A+ specification that says calling callbacks should be done asynchronously. Is there a technical problem you have difficulty with right know for proper implementation or did I understand the idea wrongly?

Cancellable promises.

Hey guys, I noticed that there's an implementation of cancellable promises in a branch. Is that something that is desired in v2? Are there any hurdles that need to be overcome?

Perhaps there is an iterative approach instead of recursive?

This library currently uses recursion for promise chain resolution. Using a promise and chaining off of the promise potentially forever, will cause a stack overflow due to deep recursion.

Here's a really contrived example:

$d = new \React\Promise\Deferred();
$p = $d->promise();
$f = function ($x) {
    echo xdebug_get_stack_depth() . ', ';
    return $x;
};

$last = $p;
for ($i = 0; $i < 1000; $i++) {
    $last = $last->then($f);
}

$d->resolve('test');

Running this code will show that the stack depth grows significantly for each promise resolution:

9, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64, 69, 74, 79, 84, 89, 94, PHP Fatal error:  Maximum function nesting level of '100' reached, aborting!

I don't usually use XDebug's default nesting limit of 100, but this illustrates the point that this library provides a recursive solution. I wonder if there is a way to implement promise resolution iteratively.

@jsor Have you ever attempted this or considered an iterative approach? It seems like some kind of directed acyclic graph structure that branches off of the deferred() would allow for an iterative approach.

Cancellation of promise collections

What is the recommended way to cancel promise collections?

This library provides several promise collection primitives, which all have valid use cases.

One might assume the following is the way to go:

$promises = array(
    accessSomeRemoteResource(),
    accessSomeRemoteResource(),
    accessSomeRemoteResource()
);

$promise = \React\Promise\all($promises);

// what does this do?
$promise->cancel();

How does this cope with cancellation support?

This is currently not implemented, i.e. calling cancel() currently has no effect.

Should we consider adding this?

Also, if yes, how could cancellation support work for the other primitives?

Unhandled promise

I think it's good that we have a method like set_exception_handler for promise that sets a user-defined unhandled promise rejection handler function

Chaining question

I have an array of items of variable length where I want to process each item by the same function after the previous promise has resolved. I just can't figure out how to code it, I keep ending up thinking in loops while I guess I shouldn't:

$items = array(...variable number of items in here...);

$this->doSomethingAsyncAndReturnAPromise($firstItem)->then(
    function () {
        $this->doSomethingAsyncAndReturnAPromise($secondItem)->then(     
            ... and so on for all the remaining $items
        );
    }
);

Can anyone point me in the right direction how to do this?

Detecting unhandled rejections

When using promises, there are a good chance that some code will forget to handle rejections/exceptions in a promise chain. This has the result of effectively hidding any rejection/exception that occurred during the execution of a promise's callback, which is dangerous and can lead to undetected bugs.

What makes this worse is that is suffices of a single error/forget in a promise chain to hide all exceptions thrown anywhere in the chain, including errors in react-php: reactphp/http-client#31, reactphp/dns#26.

It's super easy to not handle a rejection: if a promise chain is ended by anything except done(), any errors in the chain will be unhandled and potentially hidden:

$promise->always(function() {
    doSomething();
});

This example is quite obvious: any exception thrown in doSomething() is caught and hidden from the user.

Other example from react/http-client:

$promise->then(function () {
    // ok
}, function ($err) {
    $this->emit('error', $err);
    // in the 'error' callback:
    $this->retry();
})->then(function () {
    doSomething();
});

This one is less obvious. In this example, any exception thrown in retry() is caught by the promise and effectively hidden from the user.

Some promise implementations are trying to detect unhandled rejections, and log them: http://bluebirdjs.com/docs/api/error-management-configuration.html

It is possible to do so in react/promise too. For that, we would have to simply watch when promises are instantiated, handled, and destructed. If a non-handled promise is destructed, it means that an unhandled rejection occurred.

Here is a PoC adding tracing functionality to promises: arnaud-lb@90e6e35

And here is a PoC tracer that detects unhandled rejections: https://gist.github.com/arnaud-lb/a2a5a5480bbd80013f756ff968282936

This can be used like this:

<?php

$tracer = new UnhandledRejectionTracer(function ($info) {
    var_dump("unhandled promise", $info);
});
RejectedPromise::setTracer($tracer);
FulfilledPromise::setTracer($tracer);

register_shutdown_function(function () use ($tracer) {
    // handle non-destructed promises
    foreach ($tracer->getRemainingPromises() as $info) {
        var_dump("unhandled promise", $info);
    }
});

This immediately discovered a few bugs in my code. I'm using this since a few months already, and this prevents the introduction of new bugs.

Examples for writing unit tests?

I'm trying to write a unit test for code that uses Promises. I looked at the tests in this project, but I can't figure out what's happening, I was expecting to see some reference to an event loop, that I can block on, but it seems like no such things are happening?

How can I make PHPUnit block/wait until my promises resolve? For example, see the following test:

    public function testPromises() {
        $promise = new Promise(function() {
            return ['hello' => 'world'];
        });
        $promise->then(function($result) {
            $this->assertArrayHasKey('hello', $result);
        });
    }

This fails with the error "This test did not perform any assertions", which is expected, since nothing is blocking execution until the promise resolves.

How can I block PHPUnit until my promises resolve?

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);

How LazyPromise is supposed to be used with Deferred?

I am performing an RPC call over MQ. and I'd like to return the control to the code that invoked my method. The $reply->receive() call must be postponed as long as possible. It is blokcing operation and it returns the result. I cannot call $deferred->resolve immidiatly so I am thinking of doing something with LazyPromise. Something like this:

<?php

public function __invoke(Message $message, Deferred $deferred = null):Promise
{
    // do some stuff which results in $reply var. 

        return new LazyPromise(function() use ($deferred, $reply) {
            try {
                $value = JSON::decode($reply->receive($this->replyTimeout)->getBody());

                $deferred->resolve($value);
            } catch (TimeoutException $e) {
                $deferred->reject($e->getMessage());
            }

            return $deferred->promise();
        });
    }

Is this the right way to go? Is there a better solution?

done() exceptions bubble up into the wrong place

done() callbacks aren't supposed to throw exceptions, but they might, because bugs. If this happens, the exception just bubbles up into the reject() call where it shouldn't end up, because reject() isn't supposed to throw exceptions. The resolution and consumption parts should be strictly separated and not affect each other. One solution is to use trigger_error($exception, E_USER_ERROR); instead.

Additionally, an error handler could be used similar to the one proposed in async-interop to allow users to catch and log these errors but continue execution.

The following code snippet demonstrates reject() throwing while it's not supposed to throw.

<?php

require __DIR__ . "/vendor/autoload.php";

$deferred = new React\Promise\Deferred;

$deferred->promise()->done(function ($value) { }, function ($reason) {
    throw new Exception;
});

try {
    $deferred->reject("foobar");
} catch (Exception $e) {
    print "Uhm... reject isn't supposed to throw." . PHP_EOL;
}

Mark promise progress / notify() as deprecated for Promise v2

The promise progress / notify() API is deprecated and has been removed with the upcoming Promise v3 release (see #32). There's currently no schedule as to when this version is going to be released.

The current documentation for Promise v2 should include a deprecation notice to discourage people from relying on this API.

Missing example code

Hi!

I'm trying to evaluate react/promise as alternative to guzzlehttp/promise, but I can't seem to find a way to reap a promise, i.e. wait for it to be resolved and get at the according result. All I can do is to tell it what to do with the result (using then() or done()), but that just gives me another promise which is just as useful as the one I had.

I'm sure it can be done, but it's not obvious, or at least not obvious to someone who doesn't have certain background knowledge. I'd be happy if you just added a few examples that people can actually execute or use as base for their own development.

Thanks!

Uli

Question

Are the following codes same?

$promise= $deferred->promise()
    ->then(function () {
          // Logic codes
    })
    ->then(function () {
          // Logic codes
    })
    ->then(function () {
          // Logic codes
    });

And

$promise= $deferred->promise()
    ->then(function () {
          // Logic codes
    });
$promise->then(function () {
          // Logic codes
    });
$promise->then(function () {
          // Logic codes
    });

callable param can not be null in the fastcgi environment

$defer = new React\Promise\Deferred();
$defer->promise();   

I found that this code can run normally in the cli environment, but it will trigger this error in the fast-cgi(php-fpm) environment.
php version: 5.4.41
error message:

PHP Catchable fatal error:  Argument 1 passed to React\Promise\Promise::__construct() must be callable, null given, called in vendor/react/promise/src/Deferred.php on line 25 and defined in vendor/react/promise/src/Promise.php on line 16 

I guess that this is an bug of php-fpm.

How i can throw Exception from onRejected callback?

my code:

RatchetClient\connect("ws://{$this->host}:{$this->port}")->then(function(WebSocket $conn) use ($event, $data) {
    $conn->on('message', function($msg) use ($conn) {
        $conn->close();
    });

    $conn->send(json_encode(['event' => $event, 'data' => $data]));
}, function ($e) {
    // this exception never called, somebody catch it
    throw new RuntimeException("Could not connect: {$e->getMessage()}\n");
});

If my websocket server not running - nothing happen, i want see exception and stop execution.
How i can throw Exception from onRejected callback?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.