Skip to content

Commit

Permalink
Merge pull request #18 from WyriHaximus-labs/fibers-fastforward-resol…
Browse files Browse the repository at this point in the history
…ved-promise

Fast forward resolved/rejected promises with fibers await
  • Loading branch information
clue committed Jan 5, 2022
2 parents 97a6ad3 + 546cb73 commit ff11a7a
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/FiberInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface FiberInterface
{
public function resume(mixed $value): void;

public function throw(mixed $throwable): void;
public function throw(\Throwable $throwable): void;

public function suspend(): mixed;
}
8 changes: 1 addition & 7 deletions src/SimpleFiber.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,8 @@ public function resume(mixed $value): void
Loop::futureTick(fn() => $this->fiber->resume($value));
}

public function throw(mixed $throwable): void
public function throw(\Throwable $throwable): void
{
if (!$throwable instanceof \Throwable) {
$throwable = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
);
}

if ($this->fiber === null) {
Loop::futureTick(static fn() => \Fiber::suspend(static fn() => throw $throwable));
return;
Expand Down
38 changes: 35 additions & 3 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,49 @@ function async(callable $function): callable
*/
function await(PromiseInterface $promise): mixed
{
$fiber = FiberFactory::create();
$fiber = null;
$resolved = false;
$rejected = false;
$resolvedValue = null;
$rejectedThrowable = null;

$promise->then(
function (mixed $value) use (&$resolved, $fiber): void {
function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber): void {
if ($fiber === null) {
$resolved = true;
$resolvedValue = $value;
return;
}

$fiber->resume($value);
},
function (mixed $throwable) use (&$resolved, $fiber): void {
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber): void {
if (!$throwable instanceof \Throwable) {
$throwable = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
);
}

if ($fiber === null) {
$rejected = true;
$rejectedThrowable = $throwable;
return;
}

$fiber->throw($throwable);
}
);

if ($resolved) {
return $resolvedValue;
}

if ($rejected) {
throw $rejectedThrowable;
}

$fiber = FiberFactory::create();

return $fiber->suspend();
}

Expand Down
30 changes: 30 additions & 0 deletions tests/AwaitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,36 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWi
$this->assertEquals(0, gc_collect_cycles());
}

/**
* @dataProvider provideAwaiters
*/
public function testAlreadyFulfilledPromiseShouldNotSuspendFiber(callable $await)
{
for ($i = 0; $i < 6; $i++) {
$this->assertSame($i, $await(React\Promise\resolve($i)));
}
}

/**
* @dataProvider provideAwaiters
*/
public function testNestedAwaits(callable $await)
{
$this->assertTrue($await(new Promise(function ($resolve) use ($await) {
$resolve($await(new Promise(function ($resolve) use ($await) {
$resolve($await(new Promise(function ($resolve) use ($await) {
$resolve($await(new Promise(function ($resolve) use ($await) {
$resolve($await(new Promise(function ($resolve) use ($await) {
Loop::addTimer(0.01, function () use ($resolve) {
$resolve(true);
});
})));
})));
})));
})));
})));
}

public function provideAwaiters(): iterable
{
yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)];
Expand Down

0 comments on commit ff11a7a

Please sign in to comment.