Skip to content

Commit 66ce56b

Browse files
committed
Enforce throwables/exceptions as rejection reasons
1 parent dffdcec commit 66ce56b

19 files changed

+227
-255
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace React\Promise\Exception;
4+
5+
class CompositeException extends \Exception
6+
{
7+
private $exceptions;
8+
9+
public function __construct(array $exceptions, $message = '', $code = 0, $previous = null)
10+
{
11+
parent::__construct($message, $code, $previous);
12+
13+
$this->exceptions = $exceptions;
14+
}
15+
16+
/**
17+
* @return \Throwable[]|\Exception[]
18+
*/
19+
public function getExceptions()
20+
{
21+
return $this->exceptions;
22+
}
23+
24+
public static function tooManyPromisesRejected(array $reasons)
25+
{
26+
return new self(
27+
$reasons,
28+
'Too many promises rejected.'
29+
);
30+
}
31+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace React\Promise\Exception;
4+
5+
class InvalidArgumentException extends \InvalidArgumentException
6+
{
7+
public static function invalidRejectionReason($reason)
8+
{
9+
return new self(
10+
sprintf(
11+
'A Promise must be rejected with a \Throwable or \Exception instance, got "%s" instead.',
12+
is_object($reason) ? get_class($reason) : gettype($reason)
13+
)
14+
);
15+
}
16+
}

src/RejectedPromise.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
namespace React\Promise;
44

5+
use React\Promise\Exception\InvalidArgumentException;
6+
57
final class RejectedPromise implements PromiseInterface
68
{
79
private $reason;
810

9-
public function __construct($reason = null)
11+
public function __construct($reason)
1012
{
11-
if ($reason instanceof PromiseInterface) {
12-
throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
13+
if (!$reason instanceof \Throwable && !$reason instanceof \Exception) {
14+
throw InvalidArgumentException::invalidRejectionReason($reason);
1315
}
1416

1517
$this->reason = $reason;
@@ -38,13 +40,13 @@ public function done(callable $onFulfilled = null, callable $onRejected = null)
3840
{
3941
enqueue(function () use ($onRejected) {
4042
if (null === $onRejected) {
41-
throw UnhandledRejectionException::resolve($this->reason);
43+
throw $this->reason;
4244
}
4345

4446
$result = $onRejected($this->reason);
4547

4648
if ($result instanceof self) {
47-
throw UnhandledRejectionException::resolve($result->reason);
49+
throw $result->reason;
4850
}
4951

5052
if ($result instanceof PromiseInterface) {

src/UnhandledRejectionException.php

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/functions.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace React\Promise;
44

5+
use React\Promise\Exception\CompositeException;
6+
57
function resolve($promiseOrValue = null)
68
{
79
if ($promiseOrValue instanceof PromiseInterface) {
@@ -23,14 +25,8 @@ function resolve($promiseOrValue = null)
2325
return new FulfilledPromise($promiseOrValue);
2426
}
2527

26-
function reject($promiseOrValue = null)
28+
function reject($promiseOrValue)
2729
{
28-
if ($promiseOrValue instanceof PromiseInterface) {
29-
return resolve($promiseOrValue)->then(function ($value) {
30-
return new RejectedPromise($value);
31-
});
32-
}
33-
3430
return new RejectedPromise($promiseOrValue);
3531
}
3632

@@ -118,7 +114,9 @@ function some(array $promisesOrValues, $howMany)
118114
$reasons[$i] = $reason;
119115

120116
if (0 === --$toReject) {
121-
$reject($reasons);
117+
$reject(
118+
CompositeException::tooManyPromisesRejected($reasons)
119+
);
122120
}
123121
};
124122

@@ -208,10 +206,6 @@ function enqueue(callable $task)
208206
*/
209207
function _checkTypehint(callable $callback, $object)
210208
{
211-
if (!is_object($object)) {
212-
return true;
213-
}
214-
215209
if (is_array($callback)) {
216210
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
217211
} elseif (is_object($callback) && !$callback instanceof \Closure) {

tests/FunctionAllTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,16 @@ public function shouldResolveSparseArrayInput()
5959
/** @test */
6060
public function shouldRejectIfAnyInputPromiseRejects()
6161
{
62+
$exception2 = new \Exception();
63+
$exception3 = new \Exception();
64+
6265
$mock = $this->createCallableMock();
6366
$mock
6467
->expects($this->once())
6568
->method('__invoke')
66-
->with($this->identicalTo(2));
69+
->with($this->identicalTo($exception2));
6770

68-
all([resolve(1), reject(2), resolve(3)])
71+
all([resolve(1), reject($exception2), resolve($exception3)])
6972
->then($this->expectCallableNever(), $mock);
7073
}
7174

tests/FunctionAnyTest.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace React\Promise;
44

5+
use React\Promise\Exception\CompositeException;
56
use React\Promise\Exception\LengthException;
67

78
class FunctionAnyTest extends TestCase
@@ -53,26 +54,37 @@ public function shouldResolveWithAPromisedInputValue()
5354
/** @test */
5455
public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected()
5556
{
57+
$exception1 = new \Exception();
58+
$exception2 = new \Exception();
59+
$exception3 = new \Exception();
60+
61+
$compositeException = CompositeException::tooManyPromisesRejected(
62+
[0 => $exception1, 1 => $exception2, 2 => $exception3]
63+
);
64+
5665
$mock = $this->createCallableMock();
5766
$mock
5867
->expects($this->once())
5968
->method('__invoke')
60-
->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3]));
69+
->with($compositeException);
6170

62-
any([reject(1), reject(2), reject(3)])
71+
any([reject($exception1), reject($exception2), reject($exception3)])
6372
->then($this->expectCallableNever(), $mock);
6473
}
6574

6675
/** @test */
6776
public function shouldResolveWhenFirstInputPromiseResolves()
6877
{
78+
$exception2 = new \Exception();
79+
$exception3 = new \Exception();
80+
6981
$mock = $this->createCallableMock();
7082
$mock
7183
->expects($this->once())
7284
->method('__invoke')
7385
->with($this->identicalTo(1));
7486

75-
any([resolve(1), reject(2), reject(3)])
87+
any([resolve(1), reject($exception2), reject($exception3)])
7688
->then($mock);
7789
}
7890

tests/FunctionMapTest.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,17 @@ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises()
100100
/** @test */
101101
public function shouldRejectWhenInputContainsRejection()
102102
{
103+
$exception2 = new \Exception();
104+
$exception3 = new \Exception();
105+
103106
$mock = $this->createCallableMock();
104107
$mock
105108
->expects($this->once())
106109
->method('__invoke')
107-
->with($this->identicalTo(2));
110+
->with($this->identicalTo($exception2));
108111

109112
map(
110-
[resolve(1), reject(2), resolve(3)],
113+
[resolve(1), reject($exception2), resolve($exception3)],
111114
$this->mapper()
112115
)->then($this->expectCallableNever(), $mock);
113116
}

tests/FunctionRaceTest.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,13 @@ public function shouldResolveSparseArrayInput()
6666
/** @test */
6767
public function shouldRejectIfFirstSettledPromiseRejects()
6868
{
69+
$exception = new \Exception();
70+
6971
$mock = $this->createCallableMock();
7072
$mock
7173
->expects($this->once())
7274
->method('__invoke')
73-
->with($this->identicalTo(2));
75+
->with($this->identicalTo($exception));
7476

7577
$d1 = new Deferred();
7678
$d2 = new Deferred();
@@ -80,7 +82,7 @@ public function shouldRejectIfFirstSettledPromiseRejects()
8082
[$d1->promise(), $d2->promise(), $d3->promise()]
8183
)->then($this->expectCallableNever(), $mock);
8284

83-
$d2->reject(2);
85+
$d2->reject($exception);
8486

8587
$d1->resolve(1);
8688
$d3->resolve(3);
@@ -136,7 +138,7 @@ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects
136138
->method('__invoke');
137139

138140
$deferred = New Deferred($mock);
139-
$deferred->reject();
141+
$deferred->reject(new \Exception());
140142

141143
$mock2 = $this
142144
->getMockBuilder('React\Promise\PromiseInterface')

tests/FunctionReduceTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,16 @@ public function shouldReduceEmptyInputWithInitialPromise()
147147
/** @test */
148148
public function shouldRejectWhenInputContainsRejection()
149149
{
150+
$exception2 = new \Exception();
151+
150152
$mock = $this->createCallableMock();
151153
$mock
152154
->expects($this->once())
153155
->method('__invoke')
154-
->with($this->identicalTo(2));
156+
->with($this->identicalTo($exception2));
155157

156158
reduce(
157-
[resolve(1), reject(2), resolve(3)],
159+
[resolve(1), reject($exception2), resolve(3)],
158160
$this->plus(),
159161
resolve(1)
160162
)->then($this->expectCallableNever(), $mock);

0 commit comments

Comments
 (0)