diff --git a/CHANGELOG.md b/CHANGELOG.md index 11424f30..5d9b0fc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,3 +44,7 @@ CHANGELOG for 3.x \React\Promise\resolve($promise)->cancel(); } ``` + * BC break: When rejecting a promise, a `\Throwable` or `\Exception` + instance is enforced as the rejection reason (#93). + This means, it is not longer possible to reject a promise without a reason + or with another promise. diff --git a/README.md b/README.md index ed65a5ee..8ebec879 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ $deferred = new React\Promise\Deferred(); $promise = $deferred->promise(); $deferred->resolve(mixed $value = null); -$deferred->reject(mixed $reason = null); +$deferred->reject(\Throwable|\Exception $reason); ``` The `promise` method returns the promise of the deferred. @@ -128,7 +128,7 @@ this promise once it is resolved. #### Deferred::reject() ```php -$deferred->reject(mixed $reason = null); +$deferred->reject(\Throwable|\Exception $reason); ``` Rejects the promise returned by `promise()`, signalling that the deferred's @@ -136,9 +136,6 @@ computation failed. All consumers are notified by having `$onRejected` (which they registered via `$promise->then()`) called with `$reason`. -If `$reason` itself is a promise, the promise will be rejected with the outcome -of this promise regardless whether it fulfills or rejects. - ### PromiseInterface The promise interface provides the common interface for all promise @@ -359,8 +356,7 @@ Creates a already rejected promise. $promise = React\Promise\RejectedPromise($reason); ``` -Note, that `$reason` **cannot** be a promise. It's recommended to use -[reject()](#reject) for creating rejected promises. +Note, that `$reason` **must** be a `\Throwable` or `\Exception`. ### Functions @@ -390,20 +386,10 @@ If `$promiseOrValue` is a promise, it will be returned as is. #### reject() ```php -$promise = React\Promise\reject(mixed $promiseOrValue); +$promise = React\Promise\reject(\Throwable|\Exception $reason); ``` -Creates a rejected promise for the supplied `$promiseOrValue`. - -If `$promiseOrValue` is a value, it will be the rejection value of the -returned promise. - -If `$promiseOrValue` is a promise, its completion value will be the rejected -value of the returned promise. - -This can be useful in situations where you need to reject a promise without -throwing an exception. For example, it allows you to propagate a rejection with -the value of another promise. +Creates a rejected promise for the supplied `$reason`. #### all() @@ -439,7 +425,9 @@ Returns a promise that will resolve when any one of the items in 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 an array of all rejection reasons. +rejected. The rejection value will be a `React\Promise\Exception\CompositeException` +which holds all rejection reasons. The rejection reasons can be obtained with +`CompositeException::getExceptions()`. The returned promise will also reject with a `React\Promise\Exception\LengthException` if `$promisesOrValues` contains 0 items. @@ -457,8 +445,9 @@ triggering items. The returned promise will reject if it becomes impossible for `$howMany` items to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items -reject). The rejection value will be an array of -`(count($promisesOrValues) - $howMany) + 1` rejection reasons. +reject). The rejection value will be a `React\Promise\Exception\CompositeException` +which holds `(count($promisesOrValues) - $howMany) + 1` rejection reasons. +The rejection reasons can be obtained with `CompositeException::getExceptions()`. The returned promise will also reject with a `React\Promise\Exception\LengthException` if `$promisesOrValues` contains less items than `$howMany`. @@ -503,7 +492,7 @@ function getAwesomeResultPromise() $deferred = new React\Promise\Deferred(); // Execute a Node.js-style function using the callback pattern - computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) { + computeAwesomeResultAsynchronously(function (\Exception $error, $result) use ($deferred) { if ($error) { $deferred->reject($error); } else { @@ -520,7 +509,7 @@ getAwesomeResultPromise() function ($value) { // Deferred resolved, do something with $value }, - function ($reason) { + function (\Exception $reason) { // Deferred rejected, do something with $reason } ); @@ -701,11 +690,6 @@ getJsonResult() ); ``` -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 ------- diff --git a/src/Deferred.php b/src/Deferred.php index cec056d6..6bc40293 100644 --- a/src/Deferred.php +++ b/src/Deferred.php @@ -36,7 +36,7 @@ public function resolve($value = null) \call_user_func($this->resolveCallback, $value); } - public function reject($reason = null) + public function reject($reason) { $this->promise(); diff --git a/src/Exception/CompositeException.php b/src/Exception/CompositeException.php new file mode 100644 index 00000000..45af2430 --- /dev/null +++ b/src/Exception/CompositeException.php @@ -0,0 +1,30 @@ +exceptions = $exceptions; + } + + /** + * @return \Throwable[]|\Exception[] + */ + public function getExceptions() + { + return $this->exceptions; + } +} diff --git a/src/Promise.php b/src/Promise.php index c2bd262a..bfff877f 100644 --- a/src/Promise.php +++ b/src/Promise.php @@ -129,7 +129,7 @@ private function resolve($value = null) $this->settle(resolve($value)); } - private function reject($reason = null) + private function reject($reason) { if (null !== $this->result) { return; @@ -198,7 +198,7 @@ private function call(callable $callback) function ($value = null) { $this->resolve($value); }, - function ($reason = null) { + function ($reason) { $this->reject($reason); } ); diff --git a/src/RejectedPromise.php b/src/RejectedPromise.php index 7d6bf95f..638f463a 100644 --- a/src/RejectedPromise.php +++ b/src/RejectedPromise.php @@ -6,10 +6,16 @@ final class RejectedPromise implements PromiseInterface { private $reason; - public function __construct($reason = null) + public function __construct($reason) { - if ($reason instanceof PromiseInterface) { - throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.'); + if (!$reason instanceof \Throwable && !$reason instanceof \Exception) { + throw new \InvalidArgumentException( + sprintf( + 'A Promise must be rejected with a \Throwable or \Exception instance, got "%s" instead.', + is_object($reason) ? get_class($reason) : gettype($reason) + ) + + ); } $this->reason = $reason; @@ -38,9 +44,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null) { enqueue(function () use ($onRejected) { if (null === $onRejected) { - return fatalError( - UnhandledRejectionException::resolve($this->reason) - ); + return fatalError($this->reason); } try { @@ -52,9 +56,7 @@ public function done(callable $onFulfilled = null, callable $onRejected = null) } if ($result instanceof self) { - return fatalError( - UnhandledRejectionException::resolve($result->reason) - ); + return fatalError($result->reason); } if ($result instanceof PromiseInterface) { diff --git a/src/UnhandledRejectionException.php b/src/UnhandledRejectionException.php deleted file mode 100644 index e7fe2f7a..00000000 --- a/src/UnhandledRejectionException.php +++ /dev/null @@ -1,31 +0,0 @@ -reason = $reason; - - $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason)); - - parent::__construct($message, 0); - } - - public function getReason() - { - return $this->reason; - } -} diff --git a/src/functions.php b/src/functions.php index e9938d51..d6b03f6d 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,6 +2,8 @@ namespace React\Promise; +use React\Promise\Exception\CompositeException; + /** * Creates a promise for the supplied `$promiseOrValue`. * @@ -16,6 +18,7 @@ * @param mixed $promiseOrValue * @return PromiseInterface */ + function resolve($promiseOrValue = null) { if ($promiseOrValue instanceof PromiseInterface) { @@ -53,15 +56,9 @@ function resolve($promiseOrValue = null) * @param mixed $promiseOrValue * @return PromiseInterface */ -function reject($promiseOrValue = null) +function reject($reason) { - if ($promiseOrValue instanceof PromiseInterface) { - return resolve($promiseOrValue)->then(function ($value) { - return new RejectedPromise($value); - }); - } - - return new RejectedPromise($promiseOrValue); + return new RejectedPromise($reason); } /** @@ -199,7 +196,12 @@ function some(array $promisesOrValues, $howMany) $reasons[$i] = $reason; if (0 === --$toReject) { - $reject($reasons); + $reject( + new CompositeException( + $reasons, + 'Too many promises rejected.' + ) + ); } }; diff --git a/tests/FunctionAllTest.php b/tests/FunctionAllTest.php index 7eeb334b..10efdea7 100644 --- a/tests/FunctionAllTest.php +++ b/tests/FunctionAllTest.php @@ -59,13 +59,16 @@ public function shouldResolveSparseArrayInput() /** @test */ public function shouldRejectIfAnyInputPromiseRejects() { + $exception2 = new \Exception(); + $exception3 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception2)); - all([resolve(1), reject(2), resolve(3)]) + all([resolve(1), reject($exception2), resolve($exception3)]) ->then($this->expectCallableNever(), $mock); } diff --git a/tests/FunctionAnyTest.php b/tests/FunctionAnyTest.php index 969b3480..14f56ef6 100644 --- a/tests/FunctionAnyTest.php +++ b/tests/FunctionAnyTest.php @@ -2,6 +2,7 @@ namespace React\Promise; +use React\Promise\Exception\CompositeException; use React\Promise\Exception\LengthException; class FunctionAnyTest extends TestCase @@ -53,26 +54,38 @@ public function shouldResolveWithAPromisedInputValue() /** @test */ public function shouldRejectWithAllRejectedInputValuesIfAllInputsAreRejected() { + $exception1 = new \Exception(); + $exception2 = new \Exception(); + $exception3 = new \Exception(); + + $compositeException = new CompositeException( + [0 => $exception1, 1 => $exception2, 2 => $exception3], + 'Too many promises rejected.' + ); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo([0 => 1, 1 => 2, 2 => 3])); + ->with($compositeException); - any([reject(1), reject(2), reject(3)]) + any([reject($exception1), reject($exception2), reject($exception3)]) ->then($this->expectCallableNever(), $mock); } /** @test */ public function shouldResolveWhenFirstInputPromiseResolves() { + $exception2 = new \Exception(); + $exception3 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') ->with($this->identicalTo(1)); - any([resolve(1), reject(2), reject(3)]) + any([resolve(1), reject($exception2), reject($exception3)]) ->then($mock); } diff --git a/tests/FunctionMapTest.php b/tests/FunctionMapTest.php index 399a67f9..751f95b6 100644 --- a/tests/FunctionMapTest.php +++ b/tests/FunctionMapTest.php @@ -100,14 +100,17 @@ public function shouldPreserveTheOrderOfArrayWhenResolvingAsyncPromises() /** @test */ public function shouldRejectWhenInputContainsRejection() { + $exception2 = new \Exception(); + $exception3 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception2)); map( - [resolve(1), reject(2), resolve(3)], + [resolve(1), reject($exception2), resolve($exception3)], $this->mapper() )->then($this->expectCallableNever(), $mock); } diff --git a/tests/FunctionRaceTest.php b/tests/FunctionRaceTest.php index a6653e3a..289f7454 100644 --- a/tests/FunctionRaceTest.php +++ b/tests/FunctionRaceTest.php @@ -66,11 +66,13 @@ public function shouldResolveSparseArrayInput() /** @test */ public function shouldRejectIfFirstSettledPromiseRejects() { + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception)); $d1 = new Deferred(); $d2 = new Deferred(); @@ -80,7 +82,7 @@ public function shouldRejectIfFirstSettledPromiseRejects() [$d1->promise(), $d2->promise(), $d3->promise()] )->then($this->expectCallableNever(), $mock); - $d2->reject(2); + $d2->reject($exception); $d1->resolve(1); $d3->resolve(3); @@ -110,7 +112,7 @@ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfill public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseRejects() { $deferred = new Deferred($this->expectCallableNever()); - $deferred->reject(); + $deferred->reject(new \Exception()); $promise2 = new Promise(function () {}, $this->expectCallableNever()); diff --git a/tests/FunctionReduceTest.php b/tests/FunctionReduceTest.php index baf7773d..ecffe8bd 100644 --- a/tests/FunctionReduceTest.php +++ b/tests/FunctionReduceTest.php @@ -147,14 +147,16 @@ public function shouldReduceEmptyInputWithInitialPromise() /** @test */ public function shouldRejectWhenInputContainsRejection() { + $exception2 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception2)); reduce( - [resolve(1), reject(2), resolve(3)], + [resolve(1), reject($exception2), resolve(3)], $this->plus(), resolve(1) )->then($this->expectCallableNever(), $mock); diff --git a/tests/FunctionRejectTest.php b/tests/FunctionRejectTest.php index 84b8ec6a..47bf2b44 100644 --- a/tests/FunctionRejectTest.php +++ b/tests/FunctionRejectTest.php @@ -5,60 +5,26 @@ class FunctionRejectTest extends TestCase { /** @test */ - public function shouldRejectAnImmediateValue() + public function shouldRejectAnException() { - $expected = 123; + $exception = new \Exception(); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo($expected)); + ->with($this->identicalTo($exception)); - reject($expected) - ->then( - $this->expectCallableNever(), - $mock - ); + reject($exception) + ->then($this->expectCallableNever(), $mock); } - /** @test */ - public function shouldRejectAFulfilledPromise() + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function shouldThrowWhenCalledWithANonException() { - $expected = 123; - - $resolved = new FulfilledPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - reject($resolved) - ->then( - $this->expectCallableNever(), - $mock - ); - } - - /** @test */ - public function shouldRejectARejectedPromise() - { - $expected = 123; - - $resolved = new RejectedPromise($expected); - - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo($expected)); - - reject($resolved) - ->then( - $this->expectCallableNever(), - $mock - ); + reject(1); } } diff --git a/tests/FunctionResolveTest.php b/tests/FunctionResolveTest.php index b17cd161..c7802c02 100644 --- a/tests/FunctionResolveTest.php +++ b/tests/FunctionResolveTest.php @@ -74,15 +74,15 @@ public function shouldResolveACancellableThenable() /** @test */ public function shouldRejectARejectedPromise() { - $expected = 123; + $exception = new \Exception(); - $resolved = new RejectedPromise($expected); + $resolved = new RejectedPromise($exception); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo($expected)); + ->with($this->identicalTo($exception)); resolve($resolved) ->then( diff --git a/tests/FunctionSomeTest.php b/tests/FunctionSomeTest.php index e9923f4b..341948f6 100644 --- a/tests/FunctionSomeTest.php +++ b/tests/FunctionSomeTest.php @@ -2,6 +2,7 @@ namespace React\Promise; +use React\Promise\Exception\CompositeException; use React\Promise\Exception\LengthException; class FunctionSomeTest extends TestCase @@ -91,17 +92,28 @@ public function shouldResolveSparseArrayInput() )->then($mock); } - /** @test */ + /** + * @test + * @group 123 + */ public function shouldRejectIfAnyInputPromiseRejectsBeforeDesiredNumberOfInputsAreResolved() { + $exception2 = new \Exception(); + $exception3 = new \Exception(); + + $compositeException = new CompositeException( + [1 => $exception2, 2 => $exception3], + 'Too many promises rejected.' + ); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo([1 => 2, 2 => 3])); + ->with($compositeException); some( - [resolve(1), reject(2), reject(3)], + [resolve(1), reject($exception2), reject($exception3)], 2 )->then($this->expectCallableNever(), $mock); } @@ -146,7 +158,7 @@ public function shouldCancelOtherPendingInputArrayPromisesIfEnoughPromisesFulfil public function shouldNotCancelOtherPendingInputArrayPromisesIfEnoughPromisesReject() { $deferred = new Deferred($this->expectCallableNever()); - $deferred->reject(); + $deferred->reject(new \Exception()); $promise2 = new Promise(function () {}, $this->expectCallableNever()); diff --git a/tests/PromiseTest/CancelTestTrait.php b/tests/PromiseTest/CancelTestTrait.php index 478a2f5c..7b2c6053 100644 --- a/tests/PromiseTest/CancelTestTrait.php +++ b/tests/PromiseTest/CancelTestTrait.php @@ -61,15 +61,17 @@ public function cancelShouldFulfillPromiseIfCancellerFulfills() /** @test */ public function cancelShouldRejectPromiseIfCancellerRejects() { - $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) { - $reject(1); + $exception = new \Exception(); + + $adapter = $this->getPromiseTestAdapter(function ($resolve, $reject) use ($exception) { + $reject($exception); }); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->then($this->expectCallableNever(), $mock); diff --git a/tests/PromiseTest/PromiseFulfilledTestTrait.php b/tests/PromiseTest/PromiseFulfilledTestTrait.php index 2f91d3c2..f1c18025 100644 --- a/tests/PromiseTest/PromiseFulfilledTestTrait.php +++ b/tests/PromiseTest/PromiseFulfilledTestTrait.php @@ -127,17 +127,19 @@ public function thenShouldSwitchFromCallbacksToErrbacksWhenCallbackReturnsARejec { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception)); $adapter->resolve(1); $adapter->promise() ->then( - function ($val) { - return \React\Promise\reject($val + 1); + function () use ($exception) { + return \React\Promise\reject($exception); }, $this->expectCallableNever() ) @@ -228,7 +230,7 @@ public function doneShouldTriggerFatalErrorThrownFulfillmentHandlerForFulfilledP })); $errors = $errorCollector->stop(); - + $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } @@ -244,13 +246,13 @@ public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenFulfil $errorCollector->start(); $this->assertNull($adapter->promise()->done(function () { - return \React\Promise\reject(); + return \React\Promise\reject(new \Exception('Unhandled Rejection')); })); $errors = $errorCollector->stop(); $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: null', $errors[0]['errstr']); + $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } /** @test */ diff --git a/tests/PromiseTest/PromiseRejectedTestTrait.php b/tests/PromiseTest/PromiseRejectedTestTrait.php index 31ab3d55..5a456b13 100644 --- a/tests/PromiseTest/PromiseRejectedTestTrait.php +++ b/tests/PromiseTest/PromiseRejectedTestTrait.php @@ -18,14 +18,17 @@ public function rejectedPromiseShouldBeImmutable() { $adapter = $this->getPromiseTestAdapter(); + $exception1 = new \Exception(); + $exception2 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception1)); - $adapter->reject(1); - $adapter->reject(2); + $adapter->reject($exception1); + $adapter->reject($exception2); $adapter->promise() ->then( @@ -39,13 +42,15 @@ public function rejectedPromiseShouldInvokeNewlyAddedCallback() { $adapter = $this->getPromiseTestAdapter(); - $adapter->reject(1); + $exception = new \Exception(); + + $adapter->reject($exception); $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->then($this->expectCallableNever(), $mock); @@ -62,7 +67,7 @@ public function shouldForwardUndefinedRejectionValue() ->method('__invoke') ->with(null); - $adapter->reject(1); + $adapter->reject(new \Exception()); $adapter->promise() ->then( $this->expectCallableNever(), @@ -90,12 +95,12 @@ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackDoesNotExplicitlyP ->method('__invoke') ->with($this->identicalTo(2)); - $adapter->reject(1); + $adapter->reject(new \Exception()); $adapter->promise() ->then( $this->expectCallableNever(), - function ($val) { - return $val + 1; + function () { + return 2; } ) ->then( @@ -115,12 +120,12 @@ public function shouldSwitchFromErrbacksToCallbacksWhenErrbackReturnsAResolution ->method('__invoke') ->with($this->identicalTo(2)); - $adapter->reject(1); + $adapter->reject(new \Exception()); $adapter->promise() ->then( $this->expectCallableNever(), - function ($val) { - return \React\Promise\resolve($val + 1); + function () { + return \React\Promise\resolve(2); } ) ->then( @@ -148,7 +153,7 @@ public function shouldPropagateRejectionsWhenErrbackThrows() ->method('__invoke') ->with($this->identicalTo($exception)); - $adapter->reject(1); + $adapter->reject(new \Exception()); $adapter->promise() ->then( $this->expectCallableNever(), @@ -165,18 +170,20 @@ public function shouldPropagateRejectionsWhenErrbackReturnsARejection() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(2)); + ->with($this->identicalTo($exception)); - $adapter->reject(1); + $adapter->reject(new \Exception()); $adapter->promise() ->then( $this->expectCallableNever(), - function ($val) { - return \React\Promise\reject($val + 1); + function () use ($exception) { + return \React\Promise\reject($exception); } ) ->then( @@ -190,13 +197,15 @@ public function doneShouldInvokeRejectionHandlerForRejectedPromise() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); - $adapter->reject(1); + $adapter->reject($exception); $this->assertNull($adapter->promise()->done(null, $mock)); } @@ -208,7 +217,7 @@ public function doneShouldTriggerFatalErrorExceptionThrownByRejectionHandlerForR $adapter = $this->getPromiseTestAdapter(); - $adapter->reject(1); + $adapter->reject(new \Exception()); $this->assertNull($adapter->promise()->done(null, function () { throw new \Exception('Unhandled Rejection'); })); @@ -219,67 +228,6 @@ public function doneShouldTriggerFatalErrorExceptionThrownByRejectionHandlerForR $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } - /** @test */ - public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenRejectedWithNonExceptionForRejectedPromise() - { - $adapter = $this->getPromiseTestAdapter(); - - $adapter->reject(1); - - $errorCollector = new ErrorCollector(); - $errorCollector->start(); - - $this->assertNull($adapter->promise()->done()); - - $errors = $errorCollector->stop(); - - $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: 1', $errors[0]['errstr']); - } - - /** @test */ - public function unhandledRejectionExceptionThrownByDoneHoldsRejectionValue() - { - $adapter = $this->getPromiseTestAdapter(); - - $expected = new \stdClass(); - - $adapter->reject($expected); - - $errorCollector = new ErrorCollector(); - $errorCollector->start(); - - $adapter->promise()->done(); - - $errors = $errorCollector->stop(); - - $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: {}', $errors[0]['errstr']); - - $this->assertArrayHasKey('error', $errors[0]['errcontext']); - $this->assertInstanceOf('React\Promise\UnhandledRejectionException', $errors[0]['errcontext']['error']); - $this->assertSame($expected, $errors[0]['errcontext']['error']->getReason()); - } - - /** @test */ - public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenRejectionHandlerRejectsForRejectedPromise() - { - $errorCollector = new ErrorCollector(); - $errorCollector->start(); - - $adapter = $this->getPromiseTestAdapter(); - - $adapter->reject(1); - $this->assertNull($adapter->promise()->done(null, function () { - return \React\Promise\reject(); - })); - - $errors = $errorCollector->stop(); - - $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: null', $errors[0]['errstr']); - } - /** @test */ public function doneShouldTriggerFatalErrorRejectionExceptionWhenRejectionHandlerRejectsWithExceptionForRejectedPromise() { @@ -288,7 +236,7 @@ public function doneShouldTriggerFatalErrorRejectionExceptionWhenRejectionHandle $adapter = $this->getPromiseTestAdapter(); - $adapter->reject(1); + $adapter->reject(new \Exception()); $this->assertNull($adapter->promise()->done(null, function () { return \React\Promise\reject(new \Exception('Unhandled Rejection')); })); @@ -364,13 +312,15 @@ public function otherwiseShouldInvokeRejectionHandlerForRejectedPromise() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); - $adapter->reject(1); + $adapter->reject($exception); $adapter->promise()->otherwise($mock); } @@ -540,7 +490,7 @@ public function cancelShouldReturnNullForRejectedPromise() { $adapter = $this->getPromiseTestAdapter(); - $adapter->reject(); + $adapter->reject(new \Exception()); $this->assertNull($adapter->promise()->cancel()); } @@ -550,7 +500,7 @@ public function cancelShouldHaveNoEffectForRejectedPromise() { $adapter = $this->getPromiseTestAdapter($this->expectCallableNever()); - $adapter->reject(); + $adapter->reject(new \Exception()); $adapter->promise()->cancel(); } diff --git a/tests/PromiseTest/RejectTestTrait.php b/tests/PromiseTest/RejectTestTrait.php index cf0671cc..ed9c6b11 100644 --- a/tests/PromiseTest/RejectTestTrait.php +++ b/tests/PromiseTest/RejectTestTrait.php @@ -13,53 +13,54 @@ trait RejectTestTrait abstract public function getPromiseTestAdapter(callable $canceller = null); /** @test */ - public function rejectShouldRejectWithAnImmediateValue() + public function rejectShouldRejectWithAnException() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->then($this->expectCallableNever(), $mock); - $adapter->reject(1); + $adapter->reject($exception); } - /** @test */ - public function rejectShouldRejectWithFulfilledPromise() + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function rejectShouldThrowWhenCalledWithAnImmediateValue() { $adapter = $this->getPromiseTestAdapter(); - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); + $adapter->reject(1); + } - $adapter->promise() - ->then($this->expectCallableNever(), $mock); + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function rejectShouldThrowWhenCalledWithAFulfilledPromise() + { + $adapter = $this->getPromiseTestAdapter(); $adapter->reject(Promise\resolve(1)); } - /** @test */ - public function rejectShouldRejectWithRejectedPromise() + /** + * @test + * @expectedException \InvalidArgumentException + */ + public function rejectShouldThrowWhenCalledWithARejectedPromise() { $adapter = $this->getPromiseTestAdapter(); - $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($this->identicalTo(1)); - - $adapter->promise() - ->then($this->expectCallableNever(), $mock); - $adapter->reject(Promise\reject(1)); } @@ -68,11 +69,13 @@ public function rejectShouldForwardReasonWhenCallbackIsNull() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->then( @@ -83,7 +86,7 @@ public function rejectShouldForwardReasonWhenCallbackIsNull() $mock ); - $adapter->reject(1); + $adapter->reject($exception); } /** @test */ @@ -91,15 +94,19 @@ public function rejectShouldMakePromiseImmutable() { $adapter = $this->getPromiseTestAdapter(); + $exception1 = new \Exception(); + $exception2 = new \Exception(); + $exception3 = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception1)); $adapter->promise() - ->then(null, function ($value) use ($adapter) { - $adapter->reject(3); + ->then(null, function ($value) use ($exception3, $adapter) { + $adapter->reject($exception3); return Promise\reject($value); }) @@ -108,8 +115,8 @@ public function rejectShouldMakePromiseImmutable() $mock ); - $adapter->reject(1); - $adapter->reject(2); + $adapter->reject($exception1); + $adapter->reject($exception2); } /** @test */ @@ -117,16 +124,18 @@ public function rejectShouldInvokeOtherwiseHandler() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->otherwise($mock); - $adapter->reject(1); + $adapter->reject($exception); } /** @test */ @@ -134,14 +143,16 @@ public function doneShouldInvokeRejectionHandler() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $this->assertNull($adapter->promise()->done(null, $mock)); - $adapter->reject(1); + $adapter->reject($exception); } /** @test */ @@ -155,7 +166,7 @@ public function doneShouldTriggerFatalErrorExceptionThrownByRejectionHandler() $this->assertNull($adapter->promise()->done(null, function () { throw new \Exception('Unhandled Rejection'); })); - $adapter->reject(1); + $adapter->reject(new \Exception()); $errors = $errorCollector->stop(); @@ -163,42 +174,6 @@ public function doneShouldTriggerFatalErrorExceptionThrownByRejectionHandler() $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } - /** @test */ - public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenRejectedWithNonException() - { - $errorCollector = new Promise\ErrorCollector(); - $errorCollector->start(); - - $adapter = $this->getPromiseTestAdapter(); - - $this->assertNull($adapter->promise()->done()); - $adapter->reject(1); - - $errors = $errorCollector->stop(); - - $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: 1', $errors[0]['errstr']); - } - - /** @test */ - public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenRejectionHandlerRejects() - { - $errorCollector = new Promise\ErrorCollector(); - $errorCollector->start(); - - $adapter = $this->getPromiseTestAdapter(); - - $this->assertNull($adapter->promise()->done(null, function () { - return \React\Promise\reject(); - })); - $adapter->reject(1); - - $errors = $errorCollector->stop(); - - $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: null', $errors[0]['errstr']); - } - /** @test */ public function doneShouldTriggerFatalErrorRejectionExceptionWhenRejectionHandlerRejectsWithException() { @@ -210,7 +185,7 @@ public function doneShouldTriggerFatalErrorRejectionExceptionWhenRejectionHandle $this->assertNull($adapter->promise()->done(null, function () { return \React\Promise\reject(new \Exception('Unhandled Rejection')); })); - $adapter->reject(1); + $adapter->reject(new \Exception()); $errors = $errorCollector->stop(); @@ -232,13 +207,13 @@ public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenReject $this->assertNull($adapter->promise()->done(null, function () use ($promise) { return $promise; })); - $adapter->reject(1); - $d->reject(1); + $adapter->reject(new \Exception()); + $d->reject(new \Exception('Unhandled Rejection')); $errors = $errorCollector->stop(); $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: 1', $errors[0]['errstr']); + $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } /** @test */ diff --git a/tests/PromiseTest/ResolveTestTrait.php b/tests/PromiseTest/ResolveTestTrait.php index 937d6782..bfe03738 100644 --- a/tests/PromiseTest/ResolveTestTrait.php +++ b/tests/PromiseTest/ResolveTestTrait.php @@ -50,16 +50,18 @@ public function resolveShouldRejectWhenResolvedWithRejectedPromise() { $adapter = $this->getPromiseTestAdapter(); + $exception = new \Exception(); + $mock = $this->createCallableMock(); $mock ->expects($this->once()) ->method('__invoke') - ->with($this->identicalTo(1)); + ->with($this->identicalTo($exception)); $adapter->promise() ->then($this->expectCallableNever(), $mock); - $adapter->resolve(Promise\reject(1)); + $adapter->resolve(Promise\reject($exception)); } /** @test */ @@ -204,14 +206,14 @@ public function doneShouldTriggerFatalErrorUnhandledRejectionExceptionWhenFulfil $adapter = $this->getPromiseTestAdapter(); $this->assertNull($adapter->promise()->done(function () { - return \React\Promise\reject(); + return \React\Promise\reject(new \Exception('Unhandled Rejection')); })); $adapter->resolve(1); $errors = $errorCollector->stop(); $this->assertEquals(E_USER_ERROR, $errors[0]['errno']); - $this->assertContains('Unhandled Rejection: null', $errors[0]['errstr']); + $this->assertContains('Unhandled Rejection', $errors[0]['errstr']); } /** @test */ diff --git a/tests/RejectedPromiseTest.php b/tests/RejectedPromiseTest.php index 13af87aa..ddf018a5 100644 --- a/tests/RejectedPromiseTest.php +++ b/tests/RejectedPromiseTest.php @@ -31,6 +31,10 @@ public function getPromiseTestAdapter(callable $canceller = null) }, 'settle' => function ($reason = null) use (&$promise) { if (!$promise) { + if (!$reason instanceof \Exception) { + $reason = new \Exception($reason); + } + $promise = new RejectedPromise($reason); } }, @@ -39,10 +43,10 @@ public function getPromiseTestAdapter(callable $canceller = null) /** * @test - * @expectedException InvalidArgumentException + * @expectedException \InvalidArgumentException */ - public function shouldThrowExceptionIfConstructedWithAPromise() + public function shouldThrowExceptionIfConstructedWithANonException() { - return new RejectedPromise(new RejectedPromise()); + return new RejectedPromise('foo'); } }