Skip to content

Commit 4ca487c

Browse files
clueWyriHaximus
authored andcommitted
Add Fiber-based await() function
1 parent 28d9584 commit 4ca487c

File tree

4 files changed

+83
-31
lines changed

4 files changed

+83
-31
lines changed

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@
3434
"phpunit/phpunit": "^9.3"
3535
},
3636
"autoload": {
37+
"psr-4": {
38+
"React\\Async\\": "src/"
39+
},
3740
"files": [
3841
"src/functions_include.php"
3942
]

src/SimpleFiber.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace React\Async;
4+
5+
use React\EventLoop\Loop;
6+
7+
/**
8+
* @internal
9+
*/
10+
final class SimpleFiber
11+
{
12+
private ?\Fiber $fiber = null;
13+
14+
public function __construct()
15+
{
16+
$this->fiber = \Fiber::getCurrent();
17+
}
18+
19+
public function resume(mixed $value)
20+
{
21+
if ($this->fiber === null) {
22+
Loop::futureTick(static fn() => \Fiber::suspend(static fn() => $value));
23+
return;
24+
}
25+
26+
Loop::futureTick(fn() => $this->fiber->resume($value));
27+
}
28+
29+
public function throw(mixed $throwable)
30+
{
31+
if (!$throwable instanceof \Throwable) {
32+
$throwable = new \UnexpectedValueException(
33+
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable))
34+
);
35+
}
36+
37+
if ($this->fiber === null) {
38+
Loop::futureTick(static fn() => \Fiber::suspend(static fn() => throw $throwable));
39+
return;
40+
}
41+
42+
Loop::futureTick(fn() => $this->fiber->throw($throwable));
43+
}
44+
45+
public function suspend(): mixed
46+
{
47+
if ($this->fiber === null) {
48+
$fiber = fiber(static fn() => Loop::run());
49+
return ($fiber->isStarted() ? $fiber->resume() : $fiber->start())();
50+
}
51+
52+
return \Fiber::suspend();
53+
}
54+
}

src/functions.php

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use React\EventLoop\Loop;
66
use React\Promise\CancellablePromiseInterface;
77
use React\Promise\Deferred;
8+
use React\Promise\Promise;
89
use React\Promise\PromiseInterface;
910
use function React\Promise\reject;
1011
use function React\Promise\resolve;
@@ -52,48 +53,40 @@
5253
*/
5354
function await(PromiseInterface $promise): mixed
5455
{
55-
$wait = true;
56-
$resolved = null;
57-
$exception = null;
58-
$rejected = false;
56+
$fiber = new SimpleFiber();
5957

6058
$promise->then(
61-
function ($c) use (&$resolved, &$wait) {
62-
$resolved = $c;
63-
$wait = false;
64-
Loop::stop();
59+
function (mixed $value) use (&$resolved, $fiber): void {
60+
$fiber->resume($value);
6561
},
66-
function ($error) use (&$exception, &$rejected, &$wait) {
67-
$exception = $error;
68-
$rejected = true;
69-
$wait = false;
70-
Loop::stop();
62+
function (mixed $throwable) use (&$resolved, $fiber): void {
63+
$fiber->throw($throwable);
7164
}
7265
);
7366

74-
// Explicitly overwrite argument with null value. This ensure that this
75-
// argument does not show up in the stack trace in PHP 7+ only.
76-
$promise = null;
77-
78-
while ($wait) {
79-
Loop::run();
80-
}
81-
82-
if ($rejected) {
83-
// promise is rejected with an unexpected value (Promise API v1 or v2 only)
84-
if (!$exception instanceof \Throwable) {
85-
$exception = new \UnexpectedValueException(
86-
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
87-
);
88-
}
67+
return $fiber->suspend();
68+
}
8969

90-
throw $exception;
70+
/**
71+
* @internal
72+
*/
73+
function fiber(callable $callable): \Fiber
74+
{
75+
static $scheduler = null;
76+
77+
if ($scheduler === null || $scheduler->isTerminated()) {
78+
$scheduler = new \Fiber($callable);
79+
// Run event loop to completion on shutdown.
80+
\register_shutdown_function(static function () use ($scheduler): void {
81+
if ($scheduler->isSuspended()) {
82+
$scheduler->resume();
83+
}
84+
});
9185
}
9286

93-
return $resolved;
87+
return $scheduler;
9488
}
9589

96-
9790
/**
9891
* Execute a Generator-based coroutine to "await" promises.
9992
*

tests/AwaitTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public function testAwaitReturnsValueWhenPromiseIsFullfilled()
7272

7373
public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerStopsLoop()
7474
{
75+
$this->markTestIncomplete();
76+
7577
$promise = new Promise(function ($resolve) {
7678
Loop::addTimer(0.02, function () use ($resolve) {
7779
$resolve(2);

0 commit comments

Comments
 (0)