diff --git a/lib/promise-adapter/src/Adapter/GuzzleHttpPromiseAdapter.php b/lib/promise-adapter/src/Adapter/GuzzleHttpPromiseAdapter.php index 2b126ea..dd1b407 100644 --- a/lib/promise-adapter/src/Adapter/GuzzleHttpPromiseAdapter.php +++ b/lib/promise-adapter/src/Adapter/GuzzleHttpPromiseAdapter.php @@ -111,7 +111,7 @@ public function await($promise = null, $unwrap = false) }); Utils::queue()->run(); - if ($exception instanceof \Exception) { + if ($exception instanceof \Throwable) { if (!$unwrap) { return $exception; } diff --git a/lib/promise-adapter/src/Adapter/ReactPromiseAdapter.php b/lib/promise-adapter/src/Adapter/ReactPromiseAdapter.php index db247c0..8d0fd7b 100644 --- a/lib/promise-adapter/src/Adapter/ReactPromiseAdapter.php +++ b/lib/promise-adapter/src/Adapter/ReactPromiseAdapter.php @@ -105,7 +105,7 @@ public function await($promise = null, $unwrap = false) $wait = false; }); - if ($exception instanceof \Exception) { + if ($exception instanceof \Throwable) { if (!$unwrap) { return $exception; } diff --git a/lib/promise-adapter/src/Adapter/WebonyxGraphQLSyncPromiseAdapter.php b/lib/promise-adapter/src/Adapter/WebonyxGraphQLSyncPromiseAdapter.php index dbf8a65..7fb5eaa 100644 --- a/lib/promise-adapter/src/Adapter/WebonyxGraphQLSyncPromiseAdapter.php +++ b/lib/promise-adapter/src/Adapter/WebonyxGraphQLSyncPromiseAdapter.php @@ -127,10 +127,10 @@ public function await($promise = null, $unwrap = false) try { $resolvedValue = $promiseAdapter->wait($promise); - } catch (\Exception $reason) { + } catch (\Throwable $reason) { $exception = $reason; } - if ($exception instanceof \Exception) { + if ($exception instanceof \Throwable) { if (!$unwrap) { return $exception; } @@ -159,7 +159,7 @@ public function cancel($promise) } try { $canceller([$adoptedPromise, 'resolve'], [$adoptedPromise, 'reject']); - } catch (\Exception $reason) { + } catch (\Throwable $reason) { $adoptedPromise->reject($reason); } } diff --git a/lib/promise-adapter/tests/AdapterTest.php b/lib/promise-adapter/tests/AdapterTest.php index 7c341ea..e572646 100644 --- a/lib/promise-adapter/tests/AdapterTest.php +++ b/lib/promise-adapter/tests/AdapterTest.php @@ -163,6 +163,30 @@ public function testAwaitWithUnwrap(PromiseAdapterInterface $Adapter) $Adapter->await($promise, true); } + /** + * @param PromiseAdapterInterface $Adapter + */ + #[DataProvider('AdapterDataProvider')] + public function testAwaitReturnsErrorRejectionWithoutUnwrap(PromiseAdapterInterface $Adapter) + { + $expected = new \Error('error!'); + $promise = $Adapter->createRejected($expected); + + $this->assertSame($expected, $Adapter->await($promise, false)); + } + + /** + * @param PromiseAdapterInterface $Adapter + */ + #[DataProvider('AdapterDataProvider')] + public function testAwaitThrowsErrorRejectionWithUnwrap(PromiseAdapterInterface $Adapter) + { + $this->expectException(\Error::class); + $this->expectExceptionMessage('error!'); + + $Adapter->await($Adapter->createRejected(new \Error('error!')), true); + } + /** * @param PromiseAdapterInterface $Adapter */ diff --git a/src/DataLoader.php b/src/DataLoader.php index c66ae22..61e674f 100644 --- a/src/DataLoader.php +++ b/src/DataLoader.php @@ -157,9 +157,9 @@ public function prime($key, $value) // Only add the key if it does not already exist. if (!$this->promiseCache->has($cacheKey)) { - // Cache a rejected promise if the value is an Error, in order to match + // Cache a rejected promise if the value is a Throwable, in order to match // the behavior of load(key). - $promise = $value instanceof \Exception ? $this->getPromiseAdapter()->createRejected($value) : $this->getPromiseAdapter()->createFulfilled($value); + $promise = $value instanceof \Throwable ? $this->getPromiseAdapter()->createRejected($value) : $this->getPromiseAdapter()->createFulfilled($value); $this->promiseCache->set($cacheKey, $promise); } @@ -173,7 +173,7 @@ public function __destruct() foreach ($this->queue as $data) { try { $this->getPromiseAdapter()->cancel($data['promise']); - } catch (\Exception $e) { + } catch (\Throwable $e) { // no need to do nothing if cancel failed } } @@ -235,7 +235,7 @@ function ($reason) use (&$isPromiseCompleted, &$rejectedReason) { //Promise is completed? if ($isPromiseCompleted) { // rejected ? - if ($rejectedReason instanceof \Exception || (interface_exists('\Throwable') && $rejectedReason instanceof \Throwable)) { + if ($rejectedReason instanceof \Throwable) { if (!$unwrap) { return $rejectedReason; } @@ -364,7 +364,7 @@ function ($values) use ($keys, $queue) { // loaded queue. foreach ($queue as $index => $data) { $value = $values[$index]; - if ($value instanceof \Exception) { + if ($value instanceof \Throwable) { $data['reject']($value); } else { $data['resolve']($value); @@ -380,9 +380,9 @@ function ($values) use ($keys, $queue) { * Do not cache individual loads if the entire batch dispatch fails, * but still reject each request so they do not hang. * @param array $queue - * @param \Exception $error + * @param \Throwable $error */ - private function failedDispatch($queue, \Exception $error) + private function failedDispatch($queue, \Throwable $error) { foreach ($queue as $index => $data) { $this->clear($data['key']); diff --git a/src/DataLoaderInterface.php b/src/DataLoaderInterface.php index 0cad76b..4c77745 100644 --- a/src/DataLoaderInterface.php +++ b/src/DataLoaderInterface.php @@ -68,7 +68,7 @@ public function prime($key, $value); * @param $promise * @param bool $unwrap controls whether or not the value of the promise is returned for a fulfilled promise or if an exception is thrown if the promise is rejected * @return mixed - * @throws \Exception + * @throws \Throwable */ public static function await($promise = null, $unwrap = true); } diff --git a/tests/DataLoadTestCase.php b/tests/DataLoadTestCase.php index 74849f0..89d61fd 100644 --- a/tests/DataLoadTestCase.php +++ b/tests/DataLoadTestCase.php @@ -370,6 +370,47 @@ public function testHandlesPrimingTheCacheWithAnError() $this->assertEquals([], $loadCalls->getArrayCopy()); } + /** + * @group represents-errors + */ + public function testRejectsErrorUsedToPrimeTheCache() + { + list($identityLoader, $loadCalls) = self::idLoader(); + $expected = new \Error('Error: 1'); + + $identityLoader->prime(1, $expected); + + $caught = null; + try { + DataLoader::await($identityLoader->load(1)); + } catch (\Error $error) { + $caught = $error; + } + + $this->assertSame($expected, $caught); + $this->assertEquals([], $loadCalls->getArrayCopy()); + } + + /** + * @group represents-errors + */ + public function testRejectsErrorReturnedAsAnIndividualBatchValue() + { + $expected = new \Error('Error: 1'); + list($loader) = self::idLoader(null, function () use ($expected) { + return self::$promiseAdapter->createFulfilled([$expected]); + }); + + $caught = null; + try { + DataLoader::await($loader->load(1)); + } catch (\Error $error) { + $caught = $error; + } + + $this->assertSame($expected, $caught); + } + /** * @group represents-errors */ @@ -451,6 +492,24 @@ public function testPropagatesErrorToAllLoads() $this->assertEquals([[1, 2]], $loadCalls->getArrayCopy()); } + /** + * @group represents-errors + */ + public function testPropagatesErrorFromFailedBatchToAllLoads() + { + $expected = new \Error('I am a terrible loader'); + list($failLoader, $loadCalls) = self::idLoader(null, function () use ($expected) { + return self::$promiseAdapter->createRejected($expected); + }); + + $promise1 = $failLoader->load(1); + $promise2 = $failLoader->load(2); + + $this->assertSame($expected, DataLoader::await($promise1, false)); + $this->assertSame($expected, DataLoader::await($promise2, false)); + $this->assertEquals([[1, 2]], $loadCalls->getArrayCopy()); + } + /** * @group accepts-any-kind-of-key */