1
0
mirror of https://github.com/danog/amp.git synced 2024-11-30 04:29:08 +01:00

Expose combined exceptions in combinator exception

Also fix indent, escape sequences and docs
This commit is contained in:
Niklas Keller 2016-02-25 12:50:51 +01:00 committed by Bob Weinand
parent d3212d4638
commit 7046ca47e1
3 changed files with 62 additions and 26 deletions

View File

@ -2,4 +2,23 @@
namespace Amp;
class CombinatorException extends \RuntimeException {}
/**
* CombinatorException is always thrown if multiple promises are combined by combinator functions
* and an exception is thrown.
*/
class CombinatorException extends \RuntimeException {
private $exceptions;
/**
* @param string $message detailed exception message
* @param array $exceptions combined exceptions
*/
public function __construct($message, array $exceptions = []) {
parent::__construct($message, 0, null);
$this->exceptions = $exceptions;
}
public function getExceptions() {
return $this->exceptions;
}
}

View File

@ -223,7 +223,7 @@ function info() {
*
* If any one of the Promises fails the resulting Promise will immediately fail.
*
* @param array An array of promises to flatten into a single promise
* @param array $promises An array of promises to flatten into a single promise
* @return \Amp\Promise
*/
function all(array $promises) {
@ -282,7 +282,7 @@ function all(array $promises) {
* The individual keys in the resulting arrays are preserved from the initial Promise array
* passed to the function for evaluation.
*
* @param array An array of promises to flatten into a single promise
* @param array $promises An array of promises to flatten into a single promise
* @return \Amp\Promise
*/
function some(array $promises) {
@ -307,9 +307,8 @@ function some(array $promises) {
}
if (--$struct->remaining === 0) {
if (empty($struct->results)) {
array_unshift($struct->errors, "All promises passed to Amp\some() failed");
$struct->promisor->fail(new CombinatorException(
implode("\n\n", $struct->errors)
"All promises passed to Amp\\some() failed", $struct->errors
));
} else {
$struct->promisor->succeed([$struct->errors, $struct->results]);
@ -337,7 +336,7 @@ function some(array $promises) {
* This function is the same as some() with the notable exception that it will never fail even
* if all promises in the array resolve unsuccessfully.
*
* @param array An array of promises to flatten into a single promise
* @param array $promises An array of promises to flatten into a single promise
* @return \Amp\Promise
*/
function any(array $promises) {
@ -381,7 +380,7 @@ function any(array $promises) {
* Resolves with the first successful Promise value. The resulting Promise will only fail if all
* Promise values in the group fail or if the initial Promise array is empty.
*
* @param array An array of promises to flatten into a single promise
* @param array $promises An array of promises to flatten into a single promise
* @return \Amp\Promise
*/
function first(array $promises) {
@ -392,29 +391,32 @@ function first(array $promises) {
}
$struct = new \StdClass;
$struct->errors = [];
$struct->remaining = count($promises);
$struct->promisor = new Deferred;
$onResolve = function ($error, $result, $cbData) {
$struct = $cbData;
list($struct, $key) = $cbData;
if ($struct->remaining === 0) {
return;
}
if (empty($error)) {
if ($error) {
$struct->errors[$key] = $error;
} else {
$struct->remaining = 0;
$struct->promisor->succeed($result);
return;
}
if (--$struct->remaining === 0) {
$struct->promisor->fail(new CombinatorException(
"All promises failed"
"All promises failed", $struct->errors
));
}
};
foreach ($promises as $key => $promise) {
if ($promise instanceof Promise) {
$promise->when($onResolve, $struct);
$promise->when($onResolve, [$struct, $key]);
} else {
$struct->remaining = 0;
$struct->promisor->succeed($promise);
@ -426,9 +428,9 @@ function first(array $promises) {
}
/**
* Map promised deferred values using the specified functor
* Map promised deferred values using the specified functor.
*
* @param array An array of promises whose values -- once resoved -- will be mapped by the functor
* @param array $promises An array of promises whose values -- once resolved -- will be mapped by the functor
* @param callable $functor The mapping function to apply to eventual promise results
* @return \Amp\Promise
*/
@ -501,12 +503,12 @@ function map(array $promises, callable $functor) {
}
/**
* Filter deferred values using the specified functor
* Filter deferred values using the specified functor.
*
* If the functor returns a truthy value the resolved promise result is retained, otherwise it is
* discarded. Array keys are retained for any results not filtered out by the functor.
*
* @param array An array of promises whose values -- once resoved -- will be filtered by the functor
* @param array $promises An array of promises whose values -- once resolved -- will be filtered by the functor
* @param callable $functor The filtering function to apply to eventual promise results
* @return \Amp\Promise
*/
@ -593,7 +595,7 @@ function filter(array $promises, callable $functor = null) {
}
/**
* Pipe the promised value through the specified functor once it resolves
* Pipe the promised value through the specified functor once it resolves.
*
* @param mixed $promise Any value is acceptable -- non-promises are normalized to promise form
* @param callable $functor The functor through which to pipe the resolved promise value
@ -717,6 +719,7 @@ function wait(Promise $promise) {
}
if ($resolvedError) {
/** @var $resolvedError \Throwable|\Exception */
throw $resolvedError;
}

View File

@ -2,6 +2,7 @@
namespace Amp\Test;
use Amp\CombinatorException;
use Amp\NativeReactor;
use Amp\Success;
use Amp\Failure;
@ -32,7 +33,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testMapReturnsEmptySuccessOnEmptyInput() {
$promise = \Amp\map([], function () {});
$this->assertInstanceOf("Amp\Success", $promise);
$this->assertInstanceOf('Amp\Success', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -162,7 +163,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testFilterReturnsEmptySuccessOnEmptyInput() {
$promise = \Amp\filter([], function () {});
$this->assertInstanceOf("Amp\Success", $promise);
$this->assertInstanceOf('Amp\Success', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -301,6 +302,18 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
$this->assertSame(1, $invoked);
}
public function testSomeResolutionWhenAllPromisesFail() {
$ex1 = new \RuntimeException("1");
$ex2 = new \RuntimeException("2");
$promises = [new Failure($ex1), new Failure($ex2)];
\Amp\some($promises)->when(function ($e, $r) use ($ex1, $ex2) {
$this->assertNull($r);
$this->assertInstanceOf(CombinatorException::class, $e);
$this->assertSame($e->getExceptions()[0], $ex1);
$this->assertSame($e->getExceptions()[1], $ex2);
});
}
public function testAllResolutionWhenNoPromiseInstancesCombined() {
$promises = [null, 1, 2, true];
\Amp\all($promises)->when(function ($e, $r) {
@ -338,7 +351,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testAnyReturnsImmediatelyOnEmptyPromiseArray() {
$promise = \Amp\any([]);
$this->assertInstanceOf("Amp\Success", $promise);
$this->assertInstanceOf('Amp\Success', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -358,7 +371,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testAllReturnsImmediatelyOnEmptyPromiseArray() {
$promise = \Amp\all([]);
$this->assertInstanceOf("Amp\Success", $promise);
$this->assertInstanceOf('Amp\Success', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -391,7 +404,8 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
'r2' => new Failure($exception),
'r3' => new Success(40),
])->when(function ($error, $result) use ($exception) {
list($errors, $results) = (yield \Amp\some($promises));
$this->assertNull($error);
list($errors, $results) = $result;
$this->assertSame(['r2' => $exception], $errors);
$this->assertSame(['r1' => 42, 'r3' => 40], $results);
});
@ -399,7 +413,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testSomeFailsImmediatelyOnEmptyPromiseArrayInput() {
$promise = \Amp\some([]);
$this->assertInstanceOf("Amp\Failure", $promise);
$this->assertInstanceOf('Amp\Failure', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -407,7 +421,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
$result = $r;
});
$this->assertNull($result);
$this->assertInstanceOf("\LogicException", $error);
$this->assertInstanceOf('\LogicException', $error);
$this->assertSame("No promises or values provided for resolution", $error->getMessage());
}
@ -425,7 +439,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
public function testFirstFailsImmediatelyOnEmptyPromiseArrayInput() {
$promise = \Amp\first([]);
$this->assertInstanceOf("Amp\Failure", $promise);
$this->assertInstanceOf('Amp\Failure', $promise);
$error = null;
$result = null;
$promise->when(function ($e, $r) use (&$error, &$result) {
@ -433,7 +447,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
$result = $r;
});
$this->assertNull($result);
$this->assertInstanceOf("\LogicException", $error);
$this->assertInstanceOf('\LogicException', $error);
$this->assertSame("No promises or values provided", $error->getMessage());
}
@ -461,7 +475,7 @@ class FunctionsTest extends \PHPUnit_Framework_TestCase {
$promises = [$p1->promise(), $p2->promise(), $p3->promise()];
$allPromise = \Amp\all($promises);
$allPromise->when("\Amp\stop");
$allPromise->when('\Amp\stop');
$result = (yield \Amp\first($promises));
});