Skip to content

Commit b933e25

Browse files
clueWyriHaximus
authored andcommitted
Add Fiber-based async() function
1 parent 3f96c9d commit b933e25

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
@@ -10,6 +10,29 @@
1010
use function React\Promise\reject;
1111
use function React\Promise\resolve;
1212

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

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy