Skip to content

Commit

Permalink
Merge pull request #33 from SimonFrings/resolved
Browse files Browse the repository at this point in the history
[3.x] Only stop loop if a pending promise resolves/rejects
  • Loading branch information
clue authored Mar 17, 2022
2 parents c989ee1 + 42d343b commit 19998f8
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 4 deletions.
16 changes: 12 additions & 4 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,25 @@ function await(PromiseInterface $promise)
$resolved = null;
$exception = null;
$rejected = false;
$loopStarted = false;

$promise->then(
function ($c) use (&$resolved, &$wait) {
function ($c) use (&$resolved, &$wait, &$loopStarted) {
$resolved = $c;
$wait = false;
Loop::stop();

if ($loopStarted) {
Loop::stop();
}
},
function ($error) use (&$exception, &$rejected, &$wait) {
function ($error) use (&$exception, &$rejected, &$wait, &$loopStarted) {
$exception = $error;
$rejected = true;
$wait = false;
Loop::stop();

if ($loopStarted) {
Loop::stop();
}
}
);

Expand All @@ -76,6 +83,7 @@ function ($error) use (&$exception, &$rejected, &$wait) {
$promise = null;

while ($wait) {
$loopStarted = true;
Loop::run();
}

Expand Down
143 changes: 143 additions & 0 deletions tests/AwaitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,149 @@ public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerSto
$this->assertEquals(2, React\Async\await($promise));
}

public function testAwaitWithAlreadyFulfilledPromiseWillReturnWithoutRunningLoop()
{
$now = true;

Loop::futureTick(function () use (&$now) {
$now = false;
});

$promise = new Promise(function ($resolve) {
$resolve(42);
});

React\Async\await($promise);
$this->assertTrue($now);
}

public function testAwaitWithAlreadyFulfilledPromiseWillReturnWithoutStoppingLoop()
{
$ticks = 0;

$promise = new Promise(function ($resolve) {
$resolve(42);
});

// Loop will execute this tick first
Loop::futureTick(function () use (&$ticks) {
++$ticks;
// Loop will execute this tick third
Loop::futureTick(function () use (&$ticks) {
++$ticks;
});
});

// Loop will execute this tick second
Loop::futureTick(function () use (&$promise){
// await won't stop the loop if promise already resolved -> third tick will trigger
React\Async\await($promise);
});

Loop::run();

$this->assertEquals(2, $ticks);
}

public function testAwaitWithPendingPromiseThatWillResolveWillStopLoopBeforeLastTimerFinishes()
{
$promise = new Promise(function ($resolve) {
Loop::addTimer(0.02, function () use ($resolve) {
$resolve(2);
});
});

$ticks = 0;

// Loop will execute this tick first
Loop::futureTick(function () use (&$ticks) {
++$ticks;
// This timer will never finish because Loop gets stopped by await
// Loop needs to be manually started again to finish this timer
Loop::addTimer(0.04, function () use (&$ticks) {
++$ticks;
});
});

// await stops the loop when promise resolves after 0.02s
Loop::futureTick(function () use (&$promise){
React\Async\await($promise);
});

Loop::run();

// This bahvior exists in v2 & v3 of async, we recommend to use fibers in v4 (PHP>=8.1)
$this->assertEquals(1, $ticks);
}

public function testAwaitWithAlreadyRejectedPromiseWillReturnWithoutStoppingLoop()
{
$ticks = 0;

$promise = new Promise(function ($_, $reject) {
throw new \Exception();
});

// Loop will execute this tick first
Loop::futureTick(function () use (&$ticks) {
++$ticks;
// Loop will execute this tick third
Loop::futureTick(function () use (&$ticks) {
++$ticks;
});
});

// Loop will execute this tick second
Loop::futureTick(function () use (&$promise){
try {
// await won't stop the loop if promise already rejected -> third tick will trigger
React\Async\await($promise);
} catch (\Exception $e) {
// no-op
}
});

Loop::run();

$this->assertEquals(2, $ticks);
}

public function testAwaitWithPendingPromiseThatWillRejectWillStopLoopBeforeLastTimerFinishes()
{
$promise = new Promise(function ($_, $reject) {
Loop::addTimer(0.02, function () use (&$reject) {
$reject(new \Exception());
});
});

$ticks = 0;

// Loop will execute this tick first
Loop::futureTick(function () use (&$ticks) {
++$ticks;
// This timer will never finish because Loop gets stopped by await
// Loop needs to be manually started again to finish this timer
Loop::addTimer(0.04, function () use (&$ticks) {
++$ticks;
});
});

// Loop will execute this tick second
// await stops the loop when promise rejects after 0.02s
Loop::futureTick(function () use (&$promise){
try {
React\Async\await($promise);
} catch (\Exception $e) {
// no-op
}
});

Loop::run();

// This bahvior exists in v2 & v3 of async, we recommend to use fibers in v4 (PHP>=8.1)
$this->assertEquals(1, $ticks);
}

public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise()
{
if (class_exists('React\Promise\When')) {
Expand Down

0 comments on commit 19998f8

Please sign in to comment.