Skip to content

Commit ed262a9

Browse files
committed
Fast forward resolved/rejected promises with fibers await
This makes `await`ing an already resolved promise significantly faster.
1 parent 97a6ad3 commit ed262a9

File tree

4 files changed

+72
-13
lines changed

4 files changed

+72
-13
lines changed

src/FiberInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface FiberInterface
1717
{
1818
public function resume(mixed $value): void;
1919

20-
public function throw(mixed $throwable): void;
20+
public function throw(\Throwable $throwable): void;
2121

2222
public function suspend(): mixed;
2323
}

src/SimpleFiber.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,8 @@ public function resume(mixed $value): void
2727
Loop::futureTick(fn() => $this->fiber->resume($value));
2828
}
2929

30-
public function throw(mixed $throwable): void
30+
public function throw(\Throwable $throwable): void
3131
{
32-
if (!$throwable instanceof \Throwable) {
33-
$throwable = new \UnexpectedValueException(
34-
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
35-
);
36-
}
37-
3832
if ($this->fiber === null) {
3933
Loop::futureTick(static fn() => \Fiber::suspend(static fn() => throw $throwable));
4034
return;

src/functions.php

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,52 @@ function async(callable $function): callable
7777
*/
7878
function await(PromiseInterface $promise): mixed
7979
{
80-
$fiber = FiberFactory::create();
80+
$fiber = null;
81+
82+
$resolved = false;
83+
$rejected = false;
84+
$resolvedValue = null;
85+
$rejectedThrowable = null;
86+
$useFiber = false;
8187

8288
$promise->then(
83-
function (mixed $value) use (&$resolved, $fiber): void {
84-
$fiber->resume($value);
89+
function (mixed $value) use (&$resolved, &$resolvedValue, &$useFiber, &$fiber): void {
90+
$resolved = true;
91+
$resolvedValue = $value;
92+
93+
if ($useFiber) {
94+
$resolvedValue = null;
95+
$fiber->resume($value);
96+
}
8597
},
86-
function (mixed $throwable) use (&$resolved, $fiber): void {
87-
$fiber->throw($throwable);
98+
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$useFiber, &$fiber): void {
99+
if (!$throwable instanceof \Throwable) {
100+
$throwable = new \UnexpectedValueException(
101+
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
102+
);
103+
}
104+
105+
$rejected = true;
106+
$rejectedThrowable = $throwable;
107+
108+
if ($useFiber) {
109+
$rejectedThrowable = null;
110+
$fiber->throw($throwable);
111+
}
88112
}
89113
);
90114

115+
if ($resolved) {
116+
return $resolvedValue;
117+
}
118+
119+
if ($rejected) {
120+
throw $rejectedThrowable;
121+
}
122+
123+
$useFiber = true;
124+
$fiber = FiberFactory::create();
125+
91126
return $fiber->suspend();
92127
}
93128

tests/AwaitTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,36 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWi
157157
$this->assertEquals(0, gc_collect_cycles());
158158
}
159159

160+
/**
161+
* @dataProvider provideAwaiters
162+
*/
163+
public function testAlreadyFulfilledPromiseShouldNotSuspendFiber(callable $await)
164+
{
165+
for ($i = 0; $i < 6; $i++) {
166+
$this->assertSame($i, $await(React\Promise\resolve($i)));
167+
}
168+
}
169+
170+
/**
171+
* @dataProvider provideAwaiters
172+
*/
173+
public function testNestedAwaits(callable $await)
174+
{
175+
$this->assertTrue($await(new Promise(function ($resolve) use ($await) {
176+
$resolve($await(new Promise(function ($resolve) use ($await) {
177+
$resolve($await(new Promise(function ($resolve) use ($await) {
178+
$resolve($await(new Promise(function ($resolve) use ($await) {
179+
$resolve($await(new Promise(function ($resolve) use ($await) {
180+
Loop::addTimer(0.01, function () use ($resolve) {
181+
$resolve(true);
182+
});
183+
})));
184+
})));
185+
})));
186+
})));
187+
})));
188+
}
189+
160190
public function provideAwaiters(): iterable
161191
{
162192
yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)];

0 commit comments

Comments
 (0)