Skip to content

Commit fbca226

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

File tree

3 files changed

+66
-10
lines changed

3 files changed

+66
-10
lines changed

src/SimpleFiber.php

-6
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ public function resume(mixed $value): void
2929

3030
public function throw(mixed $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

+36-4
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,47 @@ function await(PromiseInterface $promise): mixed
8080
{
8181
$fiber = FiberFactory::create();
8282

83+
$resolved = false;
84+
$rejected = false;
85+
$resolvedValue = null;
86+
$rejectedThrowable = null;
87+
$useFiber = false;
88+
8389
$promise->then(
84-
function (mixed $value) use (&$resolved, $fiber): void {
85-
$fiber->resume($value);
90+
function (mixed $value) use (&$resolved, &$resolvedValue, &$useFiber, $fiber): void {
91+
$resolved = true;
92+
$resolvedValue = $value;
93+
94+
if ($useFiber) {
95+
$fiber->resume($value);
96+
}
8697
},
87-
function (mixed $throwable) use (&$resolved, $fiber): void {
88-
$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+
$fiber->throw($throwable);
110+
}
89111
}
90112
);
91113

114+
if ($resolved) {
115+
return $resolvedValue;
116+
}
117+
118+
if ($rejected) {
119+
throw $rejectedThrowable;
120+
}
121+
122+
$useFiber = true;
123+
92124
return $fiber->suspend();
93125
}
94126

tests/AwaitTest.php

+30
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)