Issue #2442411 by jhedstrom: Update react/promise to 2.2.0

8.0.x
Alex Pott 2015-03-01 14:11:33 +00:00
parent 0b5587bf86
commit 6743175a3f
32 changed files with 1798 additions and 162 deletions

12
core/composer.lock generated
View File

@ -1390,16 +1390,16 @@
},
{
"name": "react/promise",
"version": "v2.1.0",
"version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6"
"reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/937b04f1b0ee8f6d180e75a0830aac778ca4bcd6",
"reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6",
"url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef",
"reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef",
"shasum": ""
},
"require": {
@ -1416,7 +1416,7 @@
"React\\Promise\\": "src/"
},
"files": [
"src/functions.php"
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -1430,7 +1430,7 @@
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
"time": "2014-10-15 20:05:57"
"time": "2014-12-30 13:32:42"
},
{
"name": "sdboyer/gliph",

View File

@ -6,6 +6,6 @@ $vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
$vendorDir . '/react/promise/src/functions.php',
$vendorDir . '/react/promise/src/functions_include.php',
$baseDir . '/lib/Drupal.php',
);

View File

@ -329,52 +329,6 @@
"parser"
]
},
{
"name": "react/promise",
"version": "v2.1.0",
"version_normalized": "2.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/937b04f1b0ee8f6d180e75a0830aac778ca4bcd6",
"reference": "937b04f1b0ee8f6d180e75a0830aac778ca4bcd6",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"time": "2014-10-15 20:05:57",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"React\\Promise\\": "src/"
},
"files": [
"src/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@googlemail.com"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP"
},
{
"name": "guzzlehttp/streams",
"version": "3.0.0",
@ -3066,5 +3020,51 @@
"keywords": [
"scraper"
]
},
{
"name": "react/promise",
"version": "v2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/promise.git",
"reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/reactphp/promise/zipball/365fcee430dfa4ace1fbc75737ca60ceea7eeeef",
"reference": "365fcee430dfa4ace1fbc75737ca60ceea7eeeef",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"time": "2014-12-30 13:32:42",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"React\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jan Sorgalla",
"email": "jsorgalla@googlemail.com"
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP"
}
]

View File

@ -1,6 +1,18 @@
CHANGELOG
=========
* 2.2.0 (2014-12-30)
* Introduce new ExtendedPromiseInterface implemented by all promises
* Add new .done() method (part of the ExtendedPromiseInterface)
* Add new .otherwise() method (part of the ExtendedPromiseInterface)
* Add new .always() method (part of the ExtendedPromiseInterface)
* Add new .progress() method (part of the ExtendedPromiseInterface)
* Rename Deferred::progress to Deferred::notify to avoid confusion with
ExtendedPromiseInterface::progress (a Deferred::progress alias is still
available for backward compatibility)
* resolve() now always returns a ExtendedPromiseInterface
* 2.1.0 (2014-10-15)
* Introduce new CancellablePromiseInterface implemented by all promises

View File

@ -18,9 +18,14 @@ Table of Contents
* [Deferred::promise()](#deferredpromise)
* [Deferred::resolve()](#deferredresolve)
* [Deferred::reject()](#deferredreject)
* [Deferred::progress()](#deferredprogress)
* [Deferred::notify()](#deferrednotify)
* [PromiseInterface](#promiseinterface)
* [PromiseInterface::then()](#promiseinterfacethen)
* [ExtendedPromiseInterface](#extendedpromiseinterface)
* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
* [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
* [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
* [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
* [CancellablePromiseInterface](#cancellablepromiseinterface)
* [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
* [Promise](#promise-1)
@ -44,6 +49,7 @@ Table of Contents
* [Rejection forwarding](#rejection-forwarding)
* [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
* [Progress event forwarding](#progress-event-forwarding)
* [done() vs. then()](#done-vs-then)
5. [Credits](#credits)
6. [License](#license)
@ -82,28 +88,28 @@ API
A deferred represents an operation whose resolution is pending. It has separate
promise and resolver parts.
``` php
```php
$deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
$deferred->resolve(mixed $value = null);
$deferred->reject(mixed $reason = null);
$deferred->progress(mixed $update = null);
$deferred->notify(mixed $update = null);
```
The `promise` method returns the promise of the deferred.
The `resolve` and `reject` methods control the state of the deferred.
The `progress` method is for progress notification.
The `notify` method is for progress notification.
The constructor of the `Deferred` accepts an optional `$canceller` argument.
See [Promise](#promise-1) for more information.
#### Deferred::promise()
``` php
```php
$promise = $deferred->promise();
```
@ -112,7 +118,7 @@ keeping the authority to modify its state to yourself.
#### Deferred::resolve()
``` php
```php
$deferred->resolve(mixed $value = null);
```
@ -125,7 +131,7 @@ this promise once it is resolved.
#### Deferred::reject()
``` php
```php
$deferred->reject(mixed $reason = null);
```
@ -137,10 +143,10 @@ All consumers are notified by having `$onRejected` (which they registered via
If `$reason` itself is a promise, the promise will be rejected with the outcome
of this promise regardless whether it fulfills or rejects.
#### Deferred::progress()
#### Deferred::notify()
``` php
$deferred->progress(mixed $update = null);
```php
$deferred->notify(mixed $update = null);
```
Triggers progress notifications, to indicate to consumers that the computation
@ -160,14 +166,24 @@ 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.
#### Implementations
* [Promise](#promise-1)
* [FulfilledPromise](#fulfilledpromise)
* [RejectedPromise](#rejectedpromise)
* [LazyPromise](#lazypromise)
#### PromiseInterface::then()
``` php
$newPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
```php
$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = 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, rejection and progress handlers
with this promise (all parameters are optional):
with a promise (all parameters are optional):
* `$onFulfilled` will be invoked once the promise is fulfilled and passed
the result as the first argument.
@ -190,6 +206,18 @@ the same call to `then()`:
than once.
3. `$onProgress` may be called multiple times.
#### See also
* [resolve()](#resolve) - Creating a resolved promise
* [reject()](#reject) - Creating a rejected promise
* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
* [done() vs. then()](#done-vs-then)
### ExtendedPromiseInterface
The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
and utility methods which are not part of the Promises/A specification.
#### Implementations
* [Promise](#promise-1)
@ -197,10 +225,110 @@ the same call to `then()`:
* [RejectedPromise](#rejectedpromise)
* [LazyPromise](#lazypromise)
#### ExtendedPromiseInterface::done()
```php
$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
```
Consumes the promise's ultimate value if the promise fulfills, or handles the
ultimate error.
It will cause a fatal 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
* [resolve()](#resolve) - Creating a resolved promise
* [reject()](#reject) - Creating a rejected promise
* [PromiseInterface::then()](#promiseinterfacethen)
* [done() vs. then()](#done-vs-then)
#### ExtendedPromiseInterface::otherwise()
```php
$promise->otherwise(callable $onRejected);
```
Registers a rejection handler for promise. It is a shortcut for:
```php
$promise->then(null, $onRejected);
```
Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
only specific errors.
```php
$promise
->otherwise(function (\RuntimeException $reason) {
// Only catch \RuntimeException instances
// All other types of errors will propagate automatically
})
->otherwise(function ($reason) {
// Catch other errors
)};
```
#### ExtendedPromiseInterface::always()
```php
$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:
```php
try {
return doSomething();
} catch(\Exception $e) {
return handleError($e);
} finally {
cleanup();
}
```
Similar asynchronous code (with `doSomething()` that returns a promise) can be
written:
```php
return doSomething()
->otherwise('handleError')
->always('cleanup');
```
#### ExtendedPromiseInterface::progress()
```php
$promise->progress(callable $onProgress);
```
Registers a handler for progress updates from promise. It is a shortcut for:
```php
$promise->then(null, null, $onProgress);
```
### CancellablePromiseInterface
@ -232,8 +360,8 @@ a promise has no effect.
Creates a promise whose state is controlled by the functions passed to
`$resolver`.
``` php
$resolver = function (callable $resolve, callable $reject, callable $progress) {
```php
$resolver = function (callable $resolve, callable $reject, callable $notify) {
// Do some work, possibly asynchronously, and then
// resolve or reject. You can notify of progress events
// along the way if you want/need.
@ -241,7 +369,7 @@ $resolver = function (callable $resolve, callable $reject, callable $progress) {
$resolve($awesomeResult);
// or $resolve($anotherPromise);
// or $reject($nastyError);
// or $progress($progressNotification);
// or $notify($progressNotification);
};
$canceller = function (callable $resolve, callable $reject, callable $progress) {
@ -262,7 +390,7 @@ function which both will be called with 3 arguments:
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.
* `$progress($update)` - Function that issues progress events for the promise.
* `$notify($update)` - Function that issues progress events for the promise.
If the resolver or canceller throw an exception, the promise will be rejected
with that thrown exception as the rejection reason.
@ -320,20 +448,25 @@ promises.
#### resolve()
``` php
```php
$promise = React\Promise\resolve(mixed $promiseOrValue);
```
Creates a resolved promise for the supplied `$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 promise, it will simply be returned.
Note: The promise returned is always a promise implementing
[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
promise which only implements [PromiseInterface](#promiseinterface), this
promise will be assimilated to a extended promise following `$promiseOrValue`.
#### reject()
``` php
```php
$promise = React\Promise\reject(mixed $promiseOrValue);
```
@ -351,7 +484,7 @@ the value of another promise.
#### all()
``` php
```php
$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
```
@ -362,7 +495,7 @@ will be an array containing the resolution values of each of the items in
#### race()
``` php
```php
$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
```
@ -371,7 +504,7 @@ resolved in the same way the first settled promise resolves.
#### any()
``` php
```php
$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
```
@ -384,7 +517,7 @@ rejected. The rejection value will be an array of all rejection reasons.
#### some()
``` php
```php
$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
```
@ -400,7 +533,7 @@ reject). The rejection value will be an array of
#### map()
``` php
```php
$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
```
@ -412,7 +545,7 @@ value of a promise or value in `$promisesOrValues`.
#### reduce()
``` php
```php
$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
```
@ -432,7 +565,7 @@ Examples
### How to use Deferred
``` php
```php
function getAwesomeResultPromise()
{
$deferred = new React\Promise\Deferred();
@ -480,7 +613,7 @@ 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".
``` php
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
@ -520,7 +653,7 @@ 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)
``` php
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
@ -547,7 +680,7 @@ $deferred->resolve(1); // Prints "Reject 3"
Just like try/catch, you can choose to propagate or not. Mixing resolutions and
rejections will still forward handler results in a predictable way.
``` php
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
@ -587,20 +720,94 @@ chain so that they are meaningful to the next step. It also allows you to choose
not to transform them, and simply let them propagate untransformed, by not
registering a progress handler.
``` php
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
->then(null, null, function ($update) {
->progress(function ($update) {
return $update + 1;
})
->then(null, null, function ($update) {
->progress(function ($update) {
echo 'Progress ' . $update; // 2
});
$deferred->progress(1); // Prints "Progress 2"
$deferred->notify(1); // Prints "Progress 2"
```
### 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 be
rethrown in an uncatchable way causing a fatal error.
```php
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,
// a React\Promise\UnhandledRejectionException will be thrown
getJsonResult()
->done(
// Consume transformed object
function ($jsonResultObject) {
// Do something with $jsonObject
}
);
// Here we provide a rejection handler which will either throw while debugging
// or log the exception.
getJsonResult()
->done(
function ($jsonObject) {
// Do something with $jsonObject
},
function (ApiErrorException $exception) {
if (isDebug()) {
throw $e;
} else {
logException($exception);
}
}
);
```
Note that if a rejection value is not an instance of `\Exception`, it will be
wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
You can get the original rejection reason by calling `$exception->getReason()`.
Credits
-------

View File

@ -12,7 +12,7 @@
"psr-4": {
"React\\Promise\\": "src/"
},
"files": ["src/functions.php"]
"files": ["src/functions_include.php"]
},
"extra": {
"branch-alias": {

View File

@ -7,7 +7,7 @@ class Deferred implements PromisorInterface
private $promise;
private $resolveCallback;
private $rejectCallback;
private $progressCallback;
private $notifyCallback;
private $canceller;
public function __construct(callable $canceller = null)
@ -18,10 +18,10 @@ class Deferred implements PromisorInterface
public function promise()
{
if (null === $this->promise) {
$this->promise = new Promise(function ($resolve, $reject, $progress) {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
$this->progressCallback = $progress;
$this->promise = new Promise(function ($resolve, $reject, $notify) {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
$this->notifyCallback = $notify;
}, $this->canceller);
}
@ -42,10 +42,19 @@ class Deferred implements PromisorInterface
call_user_func($this->rejectCallback, $reason);
}
public function progress($update = null)
public function notify($update = null)
{
$this->promise();
call_user_func($this->progressCallback, $update);
call_user_func($this->notifyCallback, $update);
}
/**
* @deprecated 2.2.0
* @see Deferred::notify()
*/
public function progress($update = null)
{
$this->notify($update);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace React\Promise;
interface ExtendedPromiseInterface extends PromiseInterface
{
/**
* @return void
*/
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
/**
* @return ExtendedPromiseInterface
*/
public function otherwise(callable $onRejected);
/**
* @return ExtendedPromiseInterface
*/
public function always(callable $onFulfilledOrRejected);
/**
* @return ExtendedPromiseInterface
*/
public function progress(callable $onProgress);
}

View File

@ -2,7 +2,7 @@
namespace React\Promise;
class FulfilledPromise implements CancellablePromiseInterface
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $value;
@ -30,6 +30,38 @@ class FulfilledPromise implements CancellablePromiseInterface
}
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onFulfilled) {
return;
}
$result = $onFulfilled($this->value);
if ($result instanceof ExtendedPromiseInterface) {
$result->done();
}
}
public function otherwise(callable $onRejected)
{
return new FulfilledPromise($this->value);
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(function ($value) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
return $value;
});
});
}
public function progress(callable $onProgress)
{
return new FulfilledPromise($this->value);
}
public function cancel()
{
}

View File

@ -2,7 +2,7 @@
namespace React\Promise;
class LazyPromise implements CancellablePromiseInterface
class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $factory;
private $promise;
@ -17,6 +17,27 @@ class LazyPromise implements CancellablePromiseInterface
return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
}
public function otherwise(callable $onRejected)
{
return $this->promise()->otherwise($onRejected);
}
public function always(callable $onFulfilledOrRejected)
{
return $this->promise()->always($onFulfilledOrRejected);
}
public function progress(callable $onProgress)
{
return $this->promise()->progress($onProgress);
}
public function cancel()
{
return $this->promise()->cancel();

View File

@ -2,7 +2,7 @@
namespace React\Promise;
class Promise implements CancellablePromiseInterface
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $canceller;
private $result;
@ -40,6 +40,51 @@ class Promise implements CancellablePromiseInterface
});
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->done($onFulfilled, $onRejected, $onProgress);
}
$this->handlers[] = function (PromiseInterface $promise) use ($onFulfilled, $onRejected) {
$promise
->done($onFulfilled, $onRejected);
};
if ($onProgress) {
$this->progressHandlers[] = $onProgress;
}
}
public function otherwise(callable $onRejected)
{
return $this->then(null, function ($reason) use ($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new RejectedPromise($reason);
}
return $onRejected($reason);
});
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(function ($value) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
return $value;
});
}, function ($reason) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
return new RejectedPromise($reason);
});
});
}
public function progress(callable $onProgress)
{
return $this->then(null, null, $onProgress);
}
public function cancel()
{
if (null === $this->canceller || null !== $this->result) {
@ -51,23 +96,23 @@ class Promise implements CancellablePromiseInterface
private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return function ($resolve, $reject, $progress) use ($onFulfilled, $onRejected, $onProgress) {
return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
if ($onProgress) {
$progressHandler = function ($update) use ($progress, $onProgress) {
$progressHandler = function ($update) use ($notify, $onProgress) {
try {
$progress($onProgress($update));
$notify($onProgress($update));
} catch (\Exception $e) {
$progress($e);
$notify($e);
}
};
} else {
$progressHandler = $progress;
$progressHandler = $notify;
}
$this->handlers[] = function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
$promise
->then($onFulfilled, $onRejected)
->then($resolve, $reject, $progressHandler);
->done($resolve, $reject, $progressHandler);
};
$this->progressHandlers[] = $progressHandler;
@ -92,7 +137,7 @@ class Promise implements CancellablePromiseInterface
$this->settle(reject($reason));
}
private function progress($update = null)
private function notify($update = null)
{
if (null !== $this->result) {
return;
@ -103,8 +148,10 @@ class Promise implements CancellablePromiseInterface
}
}
private function settle(PromiseInterface $result)
private function settle(ExtendedPromiseInterface $promise)
{
$result = $promise;
foreach ($this->handlers as $handler) {
$handler($result);
}
@ -125,7 +172,7 @@ class Promise implements CancellablePromiseInterface
$this->reject($reason);
},
function ($update = null) {
$this->progress($update);
$this->notify($update);
}
);
} catch (\Exception $e) {

View File

@ -2,7 +2,7 @@
namespace React\Promise;
class RejectedPromise implements CancellablePromiseInterface
class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $reason;
@ -28,6 +28,46 @@ class RejectedPromise implements CancellablePromiseInterface
}
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onRejected) {
throw UnhandledRejectionException::resolve($this->reason);
}
$result = $onRejected($this->reason);
if ($result instanceof self) {
throw UnhandledRejectionException::resolve($result->reason);
}
if ($result instanceof ExtendedPromiseInterface) {
$result->done();
}
}
public function otherwise(callable $onRejected)
{
if (!_checkTypehint($onRejected, $this->reason)) {
return new RejectedPromise($this->reason);
}
return $this->then(null, $onRejected);
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
return new RejectedPromise($reason);
});
});
}
public function progress(callable $onProgress)
{
return new RejectedPromise($this->reason);
}
public function cancel()
{
}

View File

@ -0,0 +1,31 @@
<?php
namespace React\Promise;
class UnhandledRejectionException extends \RuntimeException
{
private $reason;
public static function resolve($reason)
{
if ($reason instanceof \Exception) {
return $reason;
}
return new static($reason);
}
public function __construct($reason)
{
$this->reason = $reason;
$message = sprintf('Unhandled Rejection: %s', json_encode($reason));
parent::__construct($message, 0);
}
public function getReason()
{
return $this->reason;
}
}

View File

@ -4,17 +4,23 @@ namespace React\Promise;
function resolve($promiseOrValue = null)
{
if ($promiseOrValue instanceof PromiseInterface) {
if (!$promiseOrValue instanceof PromiseInterface) {
return new FulfilledPromise($promiseOrValue);
}
if ($promiseOrValue instanceof ExtendedPromiseInterface) {
return $promiseOrValue;
}
return new FulfilledPromise($promiseOrValue);
return new Promise(function ($resolve, $reject, $notify) use ($promiseOrValue) {
$promiseOrValue->then($resolve, $reject, $notify);
});
}
function reject($promiseOrValue = null)
{
if ($promiseOrValue instanceof PromiseInterface) {
return $promiseOrValue->then(function ($value) {
return resolve($promiseOrValue)->then(function ($value) {
return new RejectedPromise($value);
});
}
@ -37,10 +43,10 @@ function race($promisesOrValues)
return resolve();
}
return new Promise(function ($resolve, $reject, $progress) use ($array) {
return new Promise(function ($resolve, $reject, $notify) use ($array) {
foreach ($array as $promiseOrValue) {
resolve($promiseOrValue)
->then($resolve, $reject, $progress);
->done($resolve, $reject, $notify);
}
});
});
@ -62,7 +68,7 @@ function some($promisesOrValues, $howMany)
return resolve([]);
}
return new Promise(function ($resolve, $reject, $progress) use ($array, $howMany) {
return new Promise(function ($resolve, $reject, $notify) use ($array, $howMany) {
$len = count($array);
$toResolve = min($howMany, $len);
$toReject = ($len - $toResolve) + 1;
@ -95,7 +101,7 @@ function some($promisesOrValues, $howMany)
};
resolve($promiseOrValue)
->then($fulfiller, $rejecter, $progress);
->done($fulfiller, $rejecter, $notify);
}
});
});
@ -109,14 +115,14 @@ function map($promisesOrValues, callable $mapFunc)
return resolve([]);
}
return new Promise(function ($resolve, $reject, $progress) use ($array, $mapFunc) {
return new Promise(function ($resolve, $reject, $notify) use ($array, $mapFunc) {
$toResolve = count($array);
$values = [];
foreach ($array as $i => $promiseOrValue) {
resolve($promiseOrValue)
->then($mapFunc)
->then(
->done(
function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
$values[$i] = $mapped;
@ -125,7 +131,7 @@ function map($promisesOrValues, callable $mapFunc)
}
},
$reject,
$progress
$notify
);
}
});
@ -158,3 +164,33 @@ function reduce($promisesOrValues, callable $reduceFunc , $initialValue = null)
return array_reduce($array, $wrappedReduceFunc, $initialValue);
});
}
// Internal functions
function _checkTypehint(callable $callback, $object)
{
if (!is_object($object)) {
return true;
}
if (is_array($callback)) {
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (is_object($callback) && !$callback instanceof \Closure) {
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
} else {
$callbackReflection = new \ReflectionFunction($callback);
}
$parameters = $callbackReflection->getParameters();
if (!isset($parameters[0])) {
return true;
}
$expectedException = $parameters[0];
if (!$expectedException->getClass()) {
return true;
}
return $expectedException->getClass()->isInstance($object);
}

View File

@ -0,0 +1,5 @@
<?php
if (!function_exists('React\Promise\resolve')) {
require __DIR__.'/functions.php';
}

View File

@ -13,11 +13,30 @@ class DeferredTest extends TestCase
$d = new Deferred($canceller);
return new CallbackPromiseAdapter([
'promise' => [$d, 'promise'],
'resolve' => [$d, 'resolve'],
'reject' => [$d, 'reject'],
'progress' => [$d, 'progress'],
'settle' => [$d, 'resolve'],
'promise' => [$d, 'promise'],
'resolve' => [$d, 'resolve'],
'reject' => [$d, 'reject'],
'notify' => [$d, 'progress'],
'settle' => [$d, 'resolve'],
]);
}
/** @test */
public function progressIsAnAliasForNotify()
{
$deferred = new Deferred();
$sentinel = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($sentinel);
$deferred->promise()
->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
$deferred->progress($sentinel);
}
}

View File

@ -29,8 +29,8 @@ class FulfilledPromiseTest extends TestCase
'reject' => function () {
throw new \LogicException('You cannot call reject() for React\Promise\FulfilledPromise');
},
'progress' => function () {
throw new \LogicException('You cannot call progress() for React\Promise\FulfilledPromise');
'notify' => function () {
// no-op
},
'settle' => function ($value = null) use (&$promise) {
if (!$promise) {

View File

@ -0,0 +1,118 @@
<?php
namespace React\Promise;
class FunctionCheckTypehintTest extends TestCase
{
/** @test */
public function shouldAcceptClosureCallbackWithTypehint()
{
$this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
}, new \InvalidArgumentException()));
$this->assertfalse(_checkTypehint(function (\InvalidArgumentException $e) {
}, new \Exception()));
}
/** @test */
public function shouldAcceptFunctionStringCallbackWithTypehint()
{
$this->assertTrue(_checkTypehint('React\Promise\testCallbackWithTypehint', new \InvalidArgumentException()));
$this->assertfalse(_checkTypehint('React\Promise\testCallbackWithTypehint', new \Exception()));
}
/** @test */
public function shouldAcceptInvokableObjectCallbackWithTypehint()
{
$this->assertTrue(_checkTypehint(new TestCallbackWithTypehintClass(), new \InvalidArgumentException()));
$this->assertfalse(_checkTypehint(new TestCallbackWithTypehintClass(), new \Exception()));
}
/** @test */
public function shouldAcceptObjectMethodCallbackWithTypehint()
{
$this->assertTrue(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
$this->assertfalse(_checkTypehint([new TestCallbackWithTypehintClass(), 'testCallback'], new \Exception()));
}
/** @test */
public function shouldAcceptStaticClassCallbackWithTypehint()
{
$this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
$this->assertfalse(_checkTypehint(['React\Promise\TestCallbackWithTypehintClass', 'testCallbackStatic'], new \Exception()));
}
/** @test */
public function shouldAcceptClosureCallbackWithoutTypehint()
{
$this->assertTrue(_checkTypehint(function (\InvalidArgumentException $e) {
}, new \InvalidArgumentException()));
}
/** @test */
public function shouldAcceptFunctionStringCallbackWithoutTypehint()
{
$this->assertTrue(_checkTypehint('React\Promise\testCallbackWithoutTypehint', new \InvalidArgumentException()));
}
/** @test */
public function shouldAcceptInvokableObjectCallbackWithoutTypehint()
{
$this->assertTrue(_checkTypehint(new TestCallbackWithoutTypehintClass(), new \InvalidArgumentException()));
}
/** @test */
public function shouldAcceptObjectMethodCallbackWithoutTypehint()
{
$this->assertTrue(_checkTypehint([new TestCallbackWithoutTypehintClass(), 'testCallback'], new \InvalidArgumentException()));
}
/** @test */
public function shouldAcceptStaticClassCallbackWithoutTypehint()
{
$this->assertTrue(_checkTypehint(['React\Promise\TestCallbackWithoutTypehintClass', 'testCallbackStatic'], new \InvalidArgumentException()));
}
}
function testCallbackWithTypehint(\InvalidArgumentException $e)
{
}
function testCallbackWithoutTypehint()
{
}
class TestCallbackWithTypehintClass
{
public function __invoke(\InvalidArgumentException $e)
{
}
public function testCallback(\InvalidArgumentException $e)
{
}
public static function testCallbackStatic(\InvalidArgumentException $e)
{
}
}
class TestCallbackWithoutTypehintClass
{
public function __invoke()
{
}
public function testCallback()
{
}
public static function testCallbackStatic()
{
}
}

View File

@ -91,4 +91,12 @@ class FunctionResolveTest extends TestCase
$result->then($mock);
}
/** @test */
public function returnsExtendePromiseForSimplePromise()
{
$promise = $this->getMock('React\Promise\PromiseInterface');
$this->assertInstanceOf('React\Promise\ExtendedPromiseInterface', resolve($promise));
}
}

View File

@ -20,10 +20,10 @@ class LazyPromiseTest extends TestCase
'promise' => function () use ($factory) {
return new LazyPromise($factory);
},
'resolve' => [$d, 'resolve'],
'reject' => [$d, 'reject'],
'progress' => [$d, 'progress'],
'settle' => [$d, 'resolve'],
'resolve' => [$d, 'resolve'],
'reject' => [$d, 'reject'],
'notify' => [$d, 'progress'],
'settle' => [$d, 'resolve'],
]);
}

View File

@ -28,9 +28,9 @@ class CallbackPromiseAdapter implements PromiseAdapterInterface
return call_user_func_array($this->callbacks['reject'], func_get_args());
}
public function progress()
public function notify()
{
return call_user_func_array($this->callbacks['progress'], func_get_args());
return call_user_func_array($this->callbacks['notify'], func_get_args());
}
public function settle()

View File

@ -9,6 +9,6 @@ interface PromiseAdapterInterface
public function promise();
public function resolve();
public function reject();
public function progress();
public function notify();
public function settle();
}

View File

@ -22,10 +22,10 @@ class PromiseTest extends TestCase
'promise' => function () use ($promise) {
return $promise;
},
'resolve' => $resolveCallback,
'reject' => $rejectCallback,
'progress' => $progressCallback,
'settle' => $resolveCallback,
'resolve' => $resolveCallback,
'reject' => $rejectCallback,
'notify' => $progressCallback,
'settle' => $resolveCallback,
]);
}
@ -47,4 +47,70 @@ class PromiseTest extends TestCase
$promise
->then($this->expectCallableNever(), $mock);
}
/** @test */
public function shouldFulfillIfFullfilledWithSimplePromise()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo('foo'));
$adapter->promise()
->then($mock);
$adapter->resolve(new SimpleFulfilledTestPromise());
}
/** @test */
public function shouldRejectIfRejectedWithSimplePromise()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo('foo'));
$adapter->promise()
->then($this->expectCallableNever(), $mock);
$adapter->resolve(new SimpleRejectedTestPromise());
}
}
class SimpleFulfilledTestPromise implements PromiseInterface
{
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
try {
if ($onFulfilled) {
$onFulfilled('foo');
}
return new self('foo');
} catch (\Exception $exception) {
return new RejectedPromise($exception);
}
}
}
class SimpleRejectedTestPromise implements PromiseInterface
{
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
try {
if ($onRejected) {
$onRejected('foo');
}
return new self('foo');
} catch (\Exception $exception) {
return new RejectedPromise($exception);
}
}
}

View File

@ -10,6 +10,6 @@ trait FullTestTrait
PromiseRejectedTestTrait,
ResolveTestTrait,
RejectTestTrait,
ProgressTestTrait,
NotifyTestTrait,
CancelTestTrait;
}

View File

@ -2,7 +2,7 @@
namespace React\Promise\PromiseTest;
trait ProgressTestTrait
trait NotifyTestTrait
{
/**
* @return \React\Promise\PromiseAdapter\PromiseAdapterInterface
@ -10,7 +10,7 @@ trait ProgressTestTrait
abstract public function getPromiseTestAdapter(callable $canceller = null);
/** @test */
public function progressShouldProgress()
public function notifyShouldProgress()
{
$adapter = $this->getPromiseTestAdapter();
@ -25,11 +25,11 @@ trait ProgressTestTrait
$adapter->promise()
->then($this->expectCallableNever(), $this->expectCallableNever(), $mock);
$adapter->progress($sentinel);
$adapter->notify($sentinel);
}
/** @test */
public function progressShouldPropagateProgressToDownstreamPromises()
public function notifyShouldPropagateProgressToDownstreamPromises()
{
$adapter = $this->getPromiseTestAdapter();
@ -59,11 +59,11 @@ trait ProgressTestTrait
$mock2
);
$adapter->progress($sentinel);
$adapter->notify($sentinel);
}
/** @test */
public function progressShouldPropagateTransformedProgressToDownstreamPromises()
public function notifyShouldPropagateTransformedProgressToDownstreamPromises()
{
$adapter = $this->getPromiseTestAdapter();
@ -93,11 +93,11 @@ trait ProgressTestTrait
$mock2
);
$adapter->progress(1);
$adapter->notify(1);
}
/** @test */
public function progressShouldPropagateCaughtExceptionValueAsProgress()
public function notifyShouldPropagateCaughtExceptionValueAsProgress()
{
$adapter = $this->getPromiseTestAdapter();
@ -127,11 +127,11 @@ trait ProgressTestTrait
$mock2
);
$adapter->progress(1);
$adapter->notify(1);
}
/** @test */
public function progressShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAResolvedPromiseReturnsAPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter2 = $this->getPromiseTestAdapter();
@ -159,11 +159,11 @@ trait ProgressTestTrait
$mock
);
$adapter2->progress($sentinel);
$adapter2->notify($sentinel);
}
/** @test */
public function progressShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
public function notifyShouldForwardProgressEventsWhenIntermediaryCallbackTiedToAnUnresolvedPromiseReturnsAPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter2 = $this->getPromiseTestAdapter();
@ -190,11 +190,11 @@ trait ProgressTestTrait
// resolve AFTER attaching progress handler
$adapter->resolve();
$adapter2->progress($sentinel);
$adapter2->notify($sentinel);
}
/** @test */
public function progressShouldForwardProgressWhenResolvedWithAnotherPromise()
public function notifyShouldForwardProgressWhenResolvedWithAnotherPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter2 = $this->getPromiseTestAdapter();
@ -226,11 +226,11 @@ trait ProgressTestTrait
);
$adapter->resolve($adapter2->promise());
$adapter2->progress($sentinel);
$adapter2->notify($sentinel);
}
/** @test */
public function progressShouldAllowResolveAfterProgress()
public function notifyShouldAllowResolveAfterProgress()
{
$adapter = $this->getPromiseTestAdapter();
@ -251,12 +251,12 @@ trait ProgressTestTrait
$mock
);
$adapter->progress(1);
$adapter->notify(1);
$adapter->resolve(2);
}
/** @test */
public function progressShouldAllowRejectAfterProgress()
public function notifyShouldAllowRejectAfterProgress()
{
$adapter = $this->getPromiseTestAdapter();
@ -277,17 +277,60 @@ trait ProgressTestTrait
$mock
);
$adapter->progress(1);
$adapter->notify(1);
$adapter->reject(2);
}
/** @test */
public function progressShouldReturnSilentlyOnProgressWhenAlreadyRejected()
public function notifyShouldReturnSilentlyOnProgressWhenAlreadyRejected()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->reject(1);
$this->assertNull($adapter->progress());
$this->assertNull($adapter->notify());
}
/** @test */
public function notifyShouldInvokeProgressHandler()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$adapter->promise()->progress($mock);
$adapter->notify(1);
}
/** @test */
public function notifyShouldInvokeProgressHandlerFromDone()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$this->assertNull($adapter->promise()->done(null, null, $mock));
$adapter->notify(1);
}
/** @test */
public function notifyShouldThrowExceptionThrownProgressHandlerFromDone()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(null, null, function () {
throw new \Exception('UnhandledRejectionException');
}));
$adapter->notify(1);
}
}

View File

@ -195,4 +195,157 @@ trait PromiseFulfilledTestTrait
$adapter->promise()->cancel();
}
/** @test */
public function doneShouldInvokeFulfillmentHandlerForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$adapter->resolve(1);
$this->assertNull($adapter->promise()->done($mock));
}
/** @test */
public function doneShouldThrowExceptionThrownFulfillmentHandlerForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$adapter->resolve(1);
$this->assertNull($adapter->promise()->done(function () {
throw new \Exception('UnhandledRejectionException');
}));
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejectsForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$adapter->resolve(1);
$this->assertNull($adapter->promise()->done(function () {
return \React\Promise\reject();
}));
}
/** @test */
public function otherwiseShouldNotInvokeRejectionHandlerForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->resolve(1);
$adapter->promise()->otherwise($this->expectCallableNever());
}
/** @test */
public function alwaysShouldNotSuppressValueForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->resolve($value);
$adapter->promise()
->always(function () {})
->then($mock);
}
/** @test */
public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromiseForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->resolve($value);
$adapter->promise()
->always(function () {
return 1;
})
->then($mock);
}
/** @test */
public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromiseForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->resolve($value);
$adapter->promise()
->always(function () {
return \React\Promise\resolve(1);
})
->then($mock);
}
/** @test */
public function alwaysShouldRejectWhenHandlerThrowsForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->resolve(1);
$adapter->promise()
->always(function () use ($exception) {
throw $exception;
})
->then(null, $mock);
}
/** @test */
public function alwaysShouldRejectWhenHandlerRejectsForFulfilledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->resolve(1);
$adapter->promise()
->always(function () use ($exception) {
return \React\Promise\reject($exception);
})
->then(null, $mock);
}
}

View File

@ -32,4 +32,37 @@ trait PromisePendingTestTrait
$this->assertNull($adapter->promise()->cancel());
}
/** @test */
public function doneShouldReturnNullForPendingPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->assertNull($adapter->promise()->done());
}
/** @test */
public function doneShouldReturnAllowNullForPendingPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->assertNull($adapter->promise()->done(null, null, null));
}
/** @test */
public function otherwiseShouldNotInvokeRejectionHandlerForPendingPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->settle();
$adapter->promise()->otherwise($this->expectCallableNever());
}
/** @test */
public function alwaysShouldReturnAPromiseForPendingPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
}
}

View File

@ -2,6 +2,8 @@
namespace React\Promise\PromiseTest;
use React\Promise\Deferred;
trait PromiseRejectedTestTrait
{
/**
@ -181,6 +183,293 @@ trait PromiseRejectedTestTrait
);
}
/** @test */
public function doneShouldInvokeRejectionHandlerForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$adapter->reject(1);
$this->assertNull($adapter->promise()->done(null, $mock));
}
/** @test */
public function doneShouldThrowExceptionThrownByRejectionHandlerForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$adapter->reject(1);
$this->assertNull($adapter->promise()->done(null, function () {
throw new \Exception('UnhandledRejectionException');
}));
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$adapter->reject(1);
$this->assertNull($adapter->promise()->done());
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$adapter->reject(1);
$this->assertNull($adapter->promise()->done(null, function () {
return \React\Promise\reject();
}));
}
/** @test */
public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$adapter->reject(1);
$this->assertNull($adapter->promise()->done(null, function () {
return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
}));
}
/** @test */
public function doneShouldThrowExceptionProvidedAsRejectionValueForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$adapter->reject(new \Exception('UnhandledRejectionException'));
$this->assertNull($adapter->promise()->done());
}
/** @test */
public function doneShouldThrowWithDeepNestingPromiseChainsForRejectedPromise()
{
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$exception = new \Exception('UnhandledRejectionException');
$d = new Deferred();
$d->resolve();
$result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
$d = new Deferred();
$d->resolve();
return \React\Promise\resolve($d->promise()->then(function () {}))->then(
function () use ($exception) {
throw $exception;
}
);
})));
$result->done();
}
/** @test */
public function doneShouldRecoverWhenRejectionHandlerCatchesExceptionForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->reject(new \Exception('UnhandledRejectionException'));
$this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
}));
}
/** @test */
public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$adapter->reject(1);
$adapter->promise()->otherwise($mock);
}
/** @test */
public function otherwiseShouldInvokeNonTypeHintedRejectionHandlerIfReasonIsAnExceptionForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->reject($exception);
$adapter->promise()
->otherwise(function ($reason) use ($mock) {
$mock($reason);
});
}
/** @test */
public function otherwiseShouldInvokeRejectionHandlerIfReasonMatchesTypehintForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \InvalidArgumentException();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->reject($exception);
$adapter->promise()
->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
$mock($reason);
});
}
/** @test */
public function otherwiseShouldNotInvokeRejectionHandlerIfReaonsDoesNotMatchTypehintForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->expectCallableNever();
$adapter->reject($exception);
$adapter->promise()
->otherwise(function (\InvalidArgumentException $reason) use ($mock) {
$mock($reason);
});
}
/** @test */
public function alwaysShouldNotSuppressRejectionForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->reject($exception);
$adapter->promise()
->always(function () {})
->then(null, $mock);
}
/** @test */
public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromiseForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->reject($exception);
$adapter->promise()
->always(function () {
return 1;
})
->then(null, $mock);
}
/** @test */
public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromiseForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->reject($exception);
$adapter->promise()
->always(function () {
return \React\Promise\resolve(1);
})
->then(null, $mock);
}
/** @test */
public function alwaysShouldRejectWhenHandlerThrowsForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception1 = new \Exception();
$exception2 = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception2));
$adapter->reject($exception1);
$adapter->promise()
->always(function () use ($exception2) {
throw $exception2;
})
->then(null, $mock);
}
/** @test */
public function alwaysShouldRejectWhenHandlerRejectsForRejectedPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception1 = new \Exception();
$exception2 = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception2));
$adapter->reject($exception1);
$adapter->promise()
->always(function () use ($exception2) {
return \React\Promise\reject($exception2);
})
->then(null, $mock);
}
/** @test */
public function cancelShouldReturnNullForRejectedPromise()
{

View File

@ -46,4 +46,41 @@ trait PromiseSettledTestTrait
$adapter->promise()->cancel();
}
/** @test */
public function doneShouldReturnNullForSettledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->settle();
$this->assertNull($adapter->promise()->done(null, function () {}));
}
/** @test */
public function doneShouldReturnAllowNullForSettledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->settle();
$this->assertNull($adapter->promise()->done(null, function () {}, null));
}
/** @test */
public function progressShouldNotInvokeProgressHandlerForSettledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->settle();
$adapter->promise()->progress($this->expectCallableNever());
$adapter->notify();
}
/** @test */
public function alwaysShouldReturnAPromiseForSettledPromise()
{
$adapter = $this->getPromiseTestAdapter();
$adapter->settle();
$this->assertInstanceOf('React\\Promise\\PromiseInterface', $adapter->promise()->always(function () {}));
}
}

View File

@ -3,6 +3,7 @@
namespace React\Promise\PromiseTest;
use React\Promise;
use React\Promise\Deferred;
trait RejectTestTrait
{
@ -105,4 +106,258 @@ trait RejectTestTrait
$adapter->reject(1);
$adapter->reject(2);
}
/** @test */
public function notifyShouldInvokeOtherwiseHandler()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$adapter->promise()
->otherwise($mock);
$adapter->reject(1);
}
/** @test */
public function doneShouldInvokeRejectionHandler()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$this->assertNull($adapter->promise()->done(null, $mock));
$adapter->reject(1);
}
/** @test */
public function doneShouldThrowExceptionThrownByRejectionHandler()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(null, function () {
throw new \Exception('UnhandledRejectionException');
}));
$adapter->reject(1);
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenRejectedWithNonException()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$this->assertNull($adapter->promise()->done());
$adapter->reject(1);
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRejects()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(null, function () {
return \React\Promise\reject();
}));
$adapter->reject(1);
}
/** @test */
public function doneShouldThrowRejectionExceptionWhenRejectionHandlerRejectsWithException()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(null, function () {
return \React\Promise\reject(new \Exception('UnhandledRejectionException'));
}));
$adapter->reject(1);
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenRejectionHandlerRetunsPendingPromiseWhichRejectsLater()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$d = new Deferred();
$promise = $d->promise();
$this->assertNull($adapter->promise()->done(null, function () use ($promise) {
return $promise;
}));
$adapter->reject(1);
$d->reject(1);
}
/** @test */
public function doneShouldThrowExceptionProvidedAsRejectionValue()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$this->assertNull($adapter->promise()->done());
$adapter->reject(new \Exception('UnhandledRejectionException'));
}
/** @test */
public function doneShouldThrowWithDeepNestingPromiseChains()
{
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$exception = new \Exception('UnhandledRejectionException');
$d = new Deferred();
$result = \React\Promise\resolve(\React\Promise\resolve($d->promise()->then(function () use ($exception) {
$d = new Deferred();
$d->resolve();
return \React\Promise\resolve($d->promise()->then(function () {}))->then(
function () use ($exception) {
throw $exception;
}
);
})));
$result->done();
$d->resolve();
}
/** @test */
public function doneShouldRecoverWhenRejectionHandlerCatchesException()
{
$adapter = $this->getPromiseTestAdapter();
$this->assertNull($adapter->promise()->done(null, function (\Exception $e) {
}));
$adapter->reject(new \Exception('UnhandledRejectionException'));
}
/** @test */
public function alwaysShouldNotSuppressRejection()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () {})
->then(null, $mock);
$adapter->reject($exception);
}
/** @test */
public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsANonPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () {
return 1;
})
->then(null, $mock);
$adapter->reject($exception);
}
/** @test */
public function alwaysShouldNotSuppressRejectionWhenHandlerReturnsAPromise()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () {
return \React\Promise\resolve(1);
})
->then(null, $mock);
$adapter->reject($exception);
}
/** @test */
public function alwaysShouldRejectWhenHandlerThrowsForRejection()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () use ($exception) {
throw $exception;
})
->then(null, $mock);
$adapter->reject($exception);
}
/** @test */
public function alwaysShouldRejectWhenHandlerRejectsForRejection()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () use ($exception) {
return \React\Promise\reject($exception);
})
->then(null, $mock);
$adapter->reject($exception);
}
}

View File

@ -106,4 +106,153 @@ trait ResolveTestTrait
$adapter->resolve(1);
$adapter->resolve(2);
}
/** @test */
public function doneShouldInvokeFulfillmentHandler()
{
$adapter = $this->getPromiseTestAdapter();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo(1));
$this->assertNull($adapter->promise()->done($mock));
$adapter->resolve(1);
}
/** @test */
public function doneShouldThrowExceptionThrownFulfillmentHandler()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('\Exception', 'UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(function () {
throw new \Exception('UnhandledRejectionException');
}));
$adapter->resolve(1);
}
/** @test */
public function doneShouldThrowUnhandledRejectionExceptionWhenFulfillmentHandlerRejects()
{
$adapter = $this->getPromiseTestAdapter();
$this->setExpectedException('React\\Promise\\UnhandledRejectionException');
$this->assertNull($adapter->promise()->done(function () {
return \React\Promise\reject();
}));
$adapter->resolve(1);
}
/** @test */
public function alwaysShouldNotSuppressValue()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->promise()
->always(function () {})
->then($mock);
$adapter->resolve($value);
}
/** @test */
public function alwaysShouldNotSuppressValueWhenHandlerReturnsANonPromise()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->promise()
->always(function () {
return 1;
})
->then($mock);
$adapter->resolve($value);
}
/** @test */
public function alwaysShouldNotSuppressValueWhenHandlerReturnsAPromise()
{
$adapter = $this->getPromiseTestAdapter();
$value = new \stdClass();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($value));
$adapter->promise()
->always(function () {
return \React\Promise\resolve(1);
})
->then($mock);
$adapter->resolve($value);
}
/** @test */
public function alwaysShouldRejectWhenHandlerThrowsForFulfillment()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () use ($exception) {
throw $exception;
})
->then(null, $mock);
$adapter->resolve(1);
}
/** @test */
public function alwaysShouldRejectWhenHandlerRejectsForFulfillment()
{
$adapter = $this->getPromiseTestAdapter();
$exception = new \Exception();
$mock = $this->createCallableMock();
$mock
->expects($this->once())
->method('__invoke')
->with($this->identicalTo($exception));
$adapter->promise()
->always(function () use ($exception) {
return \React\Promise\reject($exception);
})
->then(null, $mock);
$adapter->resolve(1);
}
}

View File

@ -29,8 +29,8 @@ class RejectedPromiseTest extends TestCase
$promise = new RejectedPromise($reason);
}
},
'progress' => function () {
throw new \LogicException('You cannot call progress() for React\Promise\RejectedPromise');
'notify' => function () {
// no-op
},
'settle' => function ($reason = null) use (&$promise) {
if (!$promise) {