Skip to content

Commit 0137470

Browse files
committed
Merge branch 'master' into enforce-exception-reasons
2 parents df0976e + c2608dd commit 0137470

17 files changed

Lines changed: 310 additions & 51 deletions

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ php:
66
- 5.6
77
- 7.0
88
- 7.1
9+
- 7.2
910
- nightly # ignore errors, see below
1011
- hhvm # ignore errors, see below
1112

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,16 @@ $resolver = function (callable $resolve, callable $reject) {
305305
// resolve or reject.
306306

307307
$resolve($awesomeResult);
308+
// or throw new Exception('Promise rejected');
308309
// or $resolve($anotherPromise);
309310
// or $reject($nastyError);
310311
};
311312

312-
$canceller = function (callable $resolve, callable $reject) {
313+
$canceller = function () {
313314
// Cancel/abort any running operations like network connections, streams etc.
314315

315-
$reject(new \Exception('Promise cancelled'));
316+
// Reject promise by throwing an exception
317+
throw new Exception('Promise cancelled');
316318
};
317319

318320
$promise = new React\Promise\Promise($resolver, $canceller);
@@ -326,7 +328,8 @@ function which both will be called with 3 arguments:
326328
When called with a non-promise value, fulfills promise with that value.
327329
When called with another promise, e.g. `$resolve($otherPromise)`, promise's
328330
fate will be equivalent to that of `$otherPromise`.
329-
* `$reject($reason)` - Function that rejects the promise.
331+
* `$reject($reason)` - Function that rejects the promise. It is recommended to
332+
just throw an exception instead of using `$reject()`.
330333

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

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"php": ">=5.4.0"
1010
},
1111
"require-dev": {
12-
"phpunit/phpunit": "~4.8"
12+
"phpunit/phpunit": "~4.8.35 || ~5.7 || ~6.4"
1313
},
1414
"autoload": {
1515
"psr-4": {

src/Deferred.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ public function resolve($value = null)
3333
{
3434
$this->promise();
3535

36-
call_user_func($this->resolveCallback, $value);
36+
\call_user_func($this->resolveCallback, $value);
3737
}
3838

3939
public function reject($reason)
4040
{
4141
$this->promise();
4242

43-
call_user_func($this->rejectCallback, $reason);
43+
\call_user_func($this->rejectCallback, $reason);
4444
}
4545
}

src/Internal/CancellationQueue.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ public function __invoke()
2222

2323
public function enqueue($cancellable)
2424
{
25-
if (!method_exists($cancellable, 'then') || !method_exists($cancellable, 'cancel')) {
25+
if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
2626
return;
2727
}
2828

29-
$length = array_push($this->queue, $cancellable);
29+
$length = \array_push($this->queue, $cancellable);
3030

3131
if ($this->started && 1 === $length) {
3232
$this->drain();
@@ -35,7 +35,7 @@ public function enqueue($cancellable)
3535

3636
private function drain()
3737
{
38-
for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
38+
for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
3939
$cancellable = $this->queue[$i];
4040

4141
$exception = null;

src/Internal/Queue.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ final class Queue
1111

1212
public function enqueue(callable $task)
1313
{
14-
if (1 === array_push($this->queue, $task)) {
14+
if (1 === \array_push($this->queue, $task)) {
1515
$this->drain();
1616
}
1717
}
1818

1919
private function drain()
2020
{
21-
for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
21+
for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
2222
$task = $this->queue[$i];
2323

2424
$exception = null;

src/Promise.php

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,15 +176,33 @@ private function unwrap($promise)
176176

177177
private function call(callable $callback)
178178
{
179+
// Use reflection to inspect number of arguments expected by this callback.
180+
// We did some careful benchmarking here: Using reflection to avoid unneeded
181+
// function arguments is actually faster than blindly passing them.
182+
// Also, this helps avoiding unnecessary function arguments in the call stack
183+
// if the callback creates an Exception (creating garbage cycles).
184+
if (\is_array($callback)) {
185+
$ref = new \ReflectionMethod($callback[0], $callback[1]);
186+
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
187+
$ref = new \ReflectionMethod($callback, '__invoke');
188+
} else {
189+
$ref = new \ReflectionFunction($callback);
190+
}
191+
$args = $ref->getNumberOfParameters();
192+
179193
try {
180-
$callback(
181-
function ($value = null) {
182-
$this->resolve($value);
183-
},
184-
function ($reason) {
185-
$this->reject($reason);
186-
}
187-
);
194+
if ($args === 0) {
195+
$callback();
196+
} else {
197+
$callback(
198+
function ($value = null) {
199+
$this->resolve($value);
200+
},
201+
function ($reason) {
202+
$this->reject($reason);
203+
}
204+
);
205+
}
188206
} catch (\Throwable $e) {
189207
$this->reject($e);
190208
} catch (\Exception $e) {

src/PromiseInterface.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,120 @@
55
interface PromiseInterface
66
{
77
/**
8+
* Transforms a promise's value by applying a function to the promise's fulfillment
9+
* or rejection value. Returns a new promise for the transformed result.
10+
*
11+
* The `then()` method registers new fulfilled and rejection handlers with a promise
12+
* (all parameters are optional):
13+
*
14+
* * `$onFulfilled` will be invoked once the promise is fulfilled and passed
15+
* the result as the first argument.
16+
* * `$onRejected` will be invoked once the promise is rejected and passed the
17+
* reason as the first argument.
18+
*
19+
* It returns a new promise that will fulfill with the return value of either
20+
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
21+
* the thrown exception if either throws.
22+
*
23+
* A promise makes the following guarantees about handlers registered in
24+
* the same call to `then()`:
25+
*
26+
* 1. Only one of `$onFulfilled` or `$onRejected` will be called,
27+
* never both.
28+
* 2. `$onFulfilled` and `$onRejected` will never be called more
29+
* than once.
30+
*
31+
* @param callable|null $onFulfilled
32+
* @param callable|null $onRejected
833
* @return PromiseInterface
934
*/
1035
public function then(callable $onFulfilled = null, callable $onRejected = null);
1136

1237
/**
38+
* Consumes the promise's ultimate value if the promise fulfills, or handles the
39+
* ultimate error.
40+
*
41+
* It will cause a fatal error (`E_USER_ERROR`) if either `$onFulfilled` or
42+
* `$onRejected` throw or return a rejected promise.
43+
*
44+
* Since the purpose of `done()` is consumption rather than transformation,
45+
* `done()` always returns `null`.
46+
*
47+
* @param callable|null $onFulfilled
48+
* @param callable|null $onRejected
1349
* @return void
1450
*/
1551
public function done(callable $onFulfilled = null, callable $onRejected = null);
1652

1753
/**
54+
* Registers a rejection handler for promise. It is a shortcut for:
55+
*
56+
* ```php
57+
* $promise->then(null, $onRejected);
58+
* ```
59+
*
60+
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
61+
* only specific errors.
62+
*
63+
* @param callable $onRejected
1864
* @return PromiseInterface
1965
*/
2066
public function otherwise(callable $onRejected);
2167

2268
/**
69+
* Allows you to execute "cleanup" type tasks in a promise chain.
70+
*
71+
* It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
72+
* when the promise is either fulfilled or rejected.
73+
*
74+
* * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
75+
* `$newPromise` will fulfill with the same value as `$promise`.
76+
* * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
77+
* rejected promise, `$newPromise` will reject with the thrown exception or
78+
* rejected promise's reason.
79+
* * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
80+
* `$newPromise` will reject with the same reason as `$promise`.
81+
* * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
82+
* rejected promise, `$newPromise` will reject with the thrown exception or
83+
* rejected promise's reason.
84+
*
85+
* `always()` behaves similarly to the synchronous finally statement. When combined
86+
* with `otherwise()`, `always()` allows you to write code that is similar to the familiar
87+
* synchronous catch/finally pair.
88+
*
89+
* Consider the following synchronous code:
90+
*
91+
* ```php
92+
* try {
93+
* return doSomething();
94+
* } catch(\Exception $e) {
95+
* return handleError($e);
96+
* } finally {
97+
* cleanup();
98+
* }
99+
* ```
100+
*
101+
* Similar asynchronous code (with `doSomething()` that returns a promise) can be
102+
* written:
103+
*
104+
* ```php
105+
* return doSomething()
106+
* ->otherwise('handleError')
107+
* ->always('cleanup');
108+
* ```
109+
*
110+
* @param callable $onFulfilledOrRejected
23111
* @return PromiseInterface
24112
*/
25113
public function always(callable $onFulfilledOrRejected);
26114

27115
/**
116+
* The `cancel()` method notifies the creator of the promise that there is no
117+
* further interest in the results of the operation.
118+
*
119+
* Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
120+
* a promise has no effect.
121+
*
28122
* @return void
29123
*/
30124
public function cancel();

src/PromisorInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
interface PromisorInterface
66
{
77
/**
8+
* Returns the promise of the deferred.
9+
*
810
* @return PromiseInterface
911
*/
1012
public function promise();

0 commit comments

Comments
 (0)