Skip to content

Commit

Permalink
Merge pull request #262 from clue-labs/unhandled-previous
Browse files Browse the repository at this point in the history
Include previous exceptions when reporting unhandled promise rejections
  • Loading branch information
WyriHaximus authored May 23, 2024
2 parents 93d0c05 + b0668bf commit 81516b6
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 6 deletions.
8 changes: 4 additions & 4 deletions src/Internal/RejectedPromise.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public function __destruct()

$handler = set_rejection_handler(null);
if ($handler === null) {
$message = 'Unhandled promise rejection with ' . \get_class($this->reason) . ': ' . $this->reason->getMessage() . ' in ' . $this->reason->getFile() . ':' . $this->reason->getLine() . PHP_EOL;
$message .= 'Stack trace:' . PHP_EOL . $this->reason->getTraceAsString();
$message = 'Unhandled promise rejection with ' . $this->reason;

\error_log($message);
return;
Expand All @@ -47,8 +46,9 @@ public function __destruct()
try {
$handler($this->reason);
} catch (\Throwable $e) {
$message = 'Fatal error: Uncaught ' . \get_class($e) . ' from unhandled promise rejection handler: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() . PHP_EOL;
$message .= 'Stack trace:' . PHP_EOL . $e->getTraceAsString();
\preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match);
\assert(isset($match[1], $match[2]));
$message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2];

\error_log($message);
exit(255);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Calling reject() without any handlers should report unhandled rejection with all previous exceptions
--INI--
# suppress legacy PHPUnit 7 warning for Xdebug 3
xdebug.default_enable=
--FILE--
<?php

use function React\Promise\reject;

require __DIR__ . '/../vendor/autoload.php';

reject(new RuntimeException('foo', 42, new \OverflowException('bar', 1000, new \InvalidArgumentException())));

?>
--EXPECTF--
Unhandled promise rejection with InvalidArgumentException in %s:%d
Stack trace:
#0 %A{main}

Next OverflowException: bar in %s:%d
Stack trace:
#0 %A{main}

Next RuntimeException: foo in %s:%d
Stack trace:
#0 %A{main}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueExcepti

?>
--EXPECTF--
Unhandled promise rejection with TypeError: Argument 1 passed to {closure}() must be an instance of UnexpectedValueException, instance of RuntimeException given, called in %s/src/Internal/RejectedPromise.php on line %d in %s:%d
Unhandled promise rejection with TypeError: Argument 1 passed to {closure}() must be an instance of UnexpectedValueException, instance of RuntimeException given, called in %s/src/Internal/RejectedPromise.php on line %d and defined in %s:%d
Stack trace:
#0 %s/src/Internal/RejectedPromise.php(%d): {closure}(%S)
#1 %s(%d): React\Promise\Internal\RejectedPromise->then(%S)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ reject(new RuntimeException('foo'))->then(null, function (UnexpectedValueExcepti

?>
--EXPECTF--
Unhandled promise rejection with TypeError: {closure%S}(): Argument #1 ($unexpected) must be of type UnexpectedValueException, RuntimeException given, called in %s/src/Internal/RejectedPromise.php on line %d in %s:%d
Unhandled promise rejection with TypeError: {closure%S}(): Argument #1 ($unexpected) must be of type UnexpectedValueException, RuntimeException given, called in %s/src/Internal/RejectedPromise.php on line %d and defined in %s:%d
Stack trace:
#0 %s/src/Internal/RejectedPromise.php(%d): {closure%S}(%S)
#1 %s(%d): React\Promise\Internal\RejectedPromise->then(%S)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
The callback given to set_rejection_handler() should not throw an exception or the program should terminate for unhandled rejection with all previous exceptions
--INI--
# suppress legacy PHPUnit 7 warning for Xdebug 3
xdebug.default_enable=
--FILE--
<?php

use function React\Promise\reject;
use function React\Promise\set_rejection_handler;

require __DIR__ . '/../vendor/autoload.php';

set_rejection_handler(function (Throwable $e): void {
throw new RuntimeException('foo', 42, new \OverflowException('bar', 1000, new \InvalidArgumentException()));
});

reject(new RuntimeException('foo'));

echo 'NEVER';

?>
--EXPECTF--
Fatal error: Uncaught InvalidArgumentException from unhandled promise rejection handler in %s:%d
Stack trace:
#0 %A{main}

Next OverflowException: bar in %s:%d
Stack trace:
#0 %A{main}

Next RuntimeException: foo in %s:%d
Stack trace:
#0 %A{main}

0 comments on commit 81516b6

Please sign in to comment.