8000 Add Fiber-based `async()` function · reactphp/async@53ebb80 · GitHub
[go: up one dir, main page]

Skip to content

Commit 53ebb80

Browse files
clueWyriHaximus
authored andcommitted
Add Fiber-based async() function
1 parent 224be28 commit 53ebb80

File tree

2 files changed

+74
-18
lines changed

2 files changed

+74
-18
lines changed

src/functions.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,29 @@
88
use React\Promise\PromiseInterface;
99
use function React\Promise\reject;
1010

11+
/**
12+
*
13+
* @template T
14+
* @param callable(...$args):T $coroutine
15+
* @param mixed ...$args
16+
* @return PromiseInterface<T>
17+
*/
18+
function async(callable $coroutine, ...$args): PromiseInterface
19+
{
20+
return new Promise(function (callable $resolve, callable $reject) use ($coroutine, $args): void {
21+
$fiber = new \Fiber(function () use ($resolve, $reject, $coroutine, $args): void {
22+
try {
23+
$resolve($coroutine(...$args));
24+
} catch (\Throwable $exception) {
25+
$reject($exception);
26+
}
27+
});
28+
29+
Loop::futureTick(static fn() => $fiber->start());
30+
});
31+
}
32+
33+
1134
/**
1235
* Block waiting for the given `$promise` to be fulfilled.
1336
*

tests/AwaitTest.php

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,24 @@
88

99
class AwaitTest extends TestCase
1010
{
11-
public function testAwaitThrowsExceptionWhenPromiseIsRejectedWithException()
11+
/**
12+
* @dataProvider provideAwaiters
13+
*/
14+
public function testAwaitThrowsExceptionWhenPromiseIsRejectedWithException(callable $await)
1215
{
1316
$promise = new Promise(function () {
1417
throw new \Exception('test');
1518
});
1619

1720
$this->expectException(\Exception::class);
1821
$this->expectExceptionMessage('test');
19-
React\Async\await($promise);
22+
$await($promise);
2023
}
2124

22-
public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithFalse()
25+
/**
26+
* @dataProvider provideAwaiters
27+
*/
28+
public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithFalse(callable $await)
2329
{
2430
if (!interface_exists('React\Promise\CancellablePromiseInterface')) {
2531
$this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3');
@@ -31,10 +37,13 @@ public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWith
3137

3238
$this->expectException(\UnexpectedValueException::class);
3339
$this->expectExceptionMessage('Promise rejected with unexpected value of type bool');
34-
React\Async\await($promise);
40+
$await($promise);
3541
}
3642

37-
public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithNull()
43+
/**
44+
* @dataProvider provideAwaiters
45+
*/
46+
public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithNull(callable $await)
3847
{
3948
if (!interface_exists('React\Promise\CancellablePromiseInterface')) {
4049
$this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3');
@@ -46,10 +55,13 @@ public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWith
4655

4756
$this->expectException(\UnexpectedValueException::class);
4857
$this->expectExceptionMessage('Promise rejected with unexpected value of type NULL');
49-
React\Async\await($promise);
58+
$await($promise);
5059
}
5160

52-
public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError()
61+
/**
62+
* @dataProvider provideAwaiters
63+
*/
64+
public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError(callable $await)
5365
{
5466
$promise = new Promise(function ($_, $reject) {
5567
throw new \Error('Test', 42);
@@ -58,19 +70,25 @@ public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError()
5870
$this->expectException(\Error::class);
5971
$this->expectExceptionMessage('Test');
6072
$this->expectExceptionCode(42);
61-
React\Async\await($promise);
73+
$await($promise);
6274
}
6375

64-
public function testAwaitReturnsValueWhenPromiseIsFullfilled()
76+
/**
77+
* @dataProvider provideAwaiters
78+
*/
79+
public function testAwaitReturnsValueWhenPromiseIsFullfilled(callable $await)
6580
{
6681
$promise = new Promise(function ($resolve) {
6782
$resolve(42);
6883
});
6984

70-
$this->assertEquals(42, React\Async\await($promise));
85+
$this->assertEquals(42, $await($promise));
7186
}
7287

73-
public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerStopsLoop()
88+
/**
89+
* @dataProvider provideAwaiters
90+
*/
91+
public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerStopsLoop(callable $await)
7492
{
7593
$this->markTestIncomplete();
7694

@@ -83,10 +101,13 @@ public function testAwaitReturnsValueWhenPromiseIsFulfilledEvenWhenOtherTimerSto
83101
Loop::stop();
84102
});
85103

86-
$this->assertEquals(2, React\Async\await($promise));
104+
$this->assertEquals(2, $await($promise));
87105
}
88106

89-
public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise()
107+
/**
108+
* @dataProvider provideAwaiters
109+
*/
110+
public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise(callable $await)
90111
{
91112
if (class_exists('React\Promise\When')) {
92113
$this->markTestSkipped('Not supported on legacy Promise v1 API');
@@ -97,13 +118,16 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise()
97118
$promise = new Promise(function ($resolve) {
98119
$resolve(42);
99120
});
100-
React\Async\await($promise);
121+
$await($promise);
101122
unset($promise);
102123

103124
$this->assertEquals(0, gc_collect_cycles());
104125
}
105126

106-
public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise()
127+
/**
128+
* @dataProvider provideAwaiters
129+
*/
130+
public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise(callable $await)
107131
{
108132
if (class_exists('React\Promise\When')) {
109133
$this->markTestSkipped('Not supported on legacy Promise v1 API');
@@ -115,7 +139,7 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise()
115139
throw new \RuntimeException();
116140
});
117141
try {
118-
React\Async\await($promise);
142+
$await($promise);
119143
} catch (\Exception $e) {
120144
// no-op
121145
}
@@ -124,7 +148,10 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise()
124148
$this->assertEquals(0, gc_collect_cycles());
125149
}
126150

127-
public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithNullValue()
151+
/**
152+
* @dataProvider provideAwaiters
153+
*/
154+
public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithNullValue(callable $await)
128155
{
129156
if (!interface_exists('React\Promise\CancellablePromiseInterface')) {
130157
$this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3');
@@ -140,12 +167,18 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWi
140167
$reject(null);
141168
});
142169
try {
143-
React\Async\await($promise);
170+
$await($promise);
144171
} catch (\Exception $e) {
145172
// no-op
146173
}
147174
unset($promise, $e);
148175

149176
$this->assertEquals(0, gc_collect_cycles());
150177
}
178+
179+
public function provideAwaiters(): iterable
180+
{
181+
yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)];
182+
yield 'async' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await(React\Async\async(static fn(): mixed => $promise))];
183+
}
151184
}

0 commit comments

Comments
 (0)
0