Skip to content

Commit

Permalink
Fast forward resolved/rejected promises with await
Browse files Browse the repository at this point in the history
This makes `await`ing an already resolved promise significantly faster.

Ported from: reactphp#18
  • Loading branch information
WyriHaximus committed Jan 25, 2022
1 parent c989ee1 commit aa33246
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 15 deletions.
52 changes: 37 additions & 15 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@
function await(PromiseInterface $promise)
{
$wait = true;
$resolved = null;
$exception = null;
$resolved = false;
$rejected = false;
$resolvedValue = null;
$rejectedThrowable = null;

$promise->then(
function ($c) use (&$resolved, &$wait) {
$resolved = $c;
function ($c) use (&$resolved, &$resolvedValue, &$wait) {
$resolvedValue = $c;
$resolved = true;
$wait = false;
Loop::stop();
},
function ($error) use (&$exception, &$rejected, &$wait) {
$exception = $error;
function ($error) use (&$rejected, &$rejectedThrowable, &$wait) {
$rejectedThrowable = $error;
$rejected = true;
$wait = false;
Loop::stop();
Expand All @@ -75,24 +77,44 @@ function ($error) use (&$exception, &$rejected, &$wait) {
// argument does not show up in the stack trace in PHP 7+ only.
$promise = null;

if ($rejected) {
awaitThrow($rejectedThrowable);
}

if ($resolved) {
return $resolvedValue;
}

while ($wait) {
Loop::run();
}

if ($rejected) {
// promise is rejected with an unexpected value (Promise API v1 or v2 only)
if (!$exception instanceof \Throwable) {
$exception = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
);
}

throw $exception;
awaitThrow($rejectedThrowable);
}

return $resolved;
return $resolvedValue;
}

/**
* This function is for internal use only. But it wraps the data a promise is rejected with in a \UnexpectedValueException
* when it isn't an \Exception or a \Throwable.
*
* @internal
* @param \Exception|\Throwable $rejectedThrowable
* @throws \Exception|\Throwable
*/
function awaitThrow($rejectedThrowable)
{
// promise is rejected with an unexpected value (Promise API v1 or v2 only)
if (!$rejectedThrowable instanceof \Exception && !$rejectedThrowable instanceof \Throwable) {
$rejectedThrowable = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($rejectedThrowable) ? get_class($rejectedThrowable) : gettype($rejectedThrowable))
);
}

throw $rejectedThrowable;
}

/**
* Execute a Generator-based coroutine to "await" promises.
Expand Down
7 changes: 7 additions & 0 deletions tests/AwaitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise()
$this->assertEquals(0, gc_collect_cycles());
}

public function testAlreadyFulfilledPromiseShouldNotSuspendFiber()
{
for ($i = 0; $i < 6; $i++) {
$this->assertSame($i, React\Async\await(React\Promise\resolve($i)));
}
}

public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithNullValue()
{
if (!interface_exists('React\Promise\CancellablePromiseInterface')) {
Expand Down

0 comments on commit aa33246

Please sign in to comment.