Skip to content

Commit 97770fa

Browse files
committed
Import await() function from clue/reactphp-block v1.5.0
Change namespace from `Clue\React\Block` to `React\Async` and update all tests with merged namespaces. See https://github.com/clue/reactphp-block for original repo.
1 parent 6dcdf94 commit 97770fa

File tree

4 files changed

+494
-7
lines changed

4 files changed

+494
-7
lines changed

README.md

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ an event loop, it can be used with this library.
1616
**Table of Contents**
1717

1818
* [Usage](#usage)
19+
* [await()](#await)
1920
* [parallel()](#parallel)
2021
* [series()](#series)
2122
* [waterfall()](#waterfall)
@@ -32,25 +33,80 @@ All functions reside under the `React\Async` namespace.
3233
The below examples refer to all functions with their fully-qualified names like this:
3334

3435
```php
35-
React\Async\parallel(…);
36+
React\Async\await(…);
3637
```
3738

3839
As of PHP 5.6+ you can also import each required function into your code like this:
3940

4041
```php
41-
use function React\Async\parallel;
42+
use function React\Async\await;
4243

43-
parallel(…);
44+
await(…);
4445
```
4546

4647
Alternatively, you can also use an import statement similar to this:
4748

4849
```php
4950
use React\Async;
5051

51-
Async\parallel(…);
52+
Async\await(…);
5253
```
5354

55+
### await()
56+
57+
The `await(PromiseInterface $promise, ?LoopInterface $loop = null, ?float $timeout = null): mixed` function can be used to
58+
block waiting for the given `$promise` to be fulfilled.
59+
60+
```php
61+
$result = React\Async\await($promise);
62+
```
63+
64+
This function will only return after the given `$promise` has settled, i.e.
65+
either fulfilled or rejected. In the meantime, the event loop will run any
66+
events attached to the same loop until the promise settles.
67+
68+
Once the promise is fulfilled, this function will return whatever the promise
69+
resolved to.
70+
71+
Once the promise is rejected, this will throw whatever the promise rejected
72+
with. If the promise did not reject with an `Exception`, then this function
73+
will throw an `UnexpectedValueException` instead.
74+
75+
```php
76+
try {
77+
$result = React\Async\await($promise);
78+
// promise successfully fulfilled with $result
79+
echo 'Result: ' . $result;
80+
} catch (Exception $exception) {
81+
// promise rejected with $exception
82+
echo 'ERROR: ' . $exception->getMessage();
83+
}
84+
```
85+
86+
This function takes an optional `LoopInterface|null $loop` parameter that can be used to
87+
pass the event loop instance to use. You can use a `null` value here in order to
88+
use the [default loop](https://github.com/reactphp/event-loop#loop). This value
89+
SHOULD NOT be given unless you're sure you want to explicitly use a given event
90+
loop instance.
91+
92+
If no `$timeout` argument is given and the promise stays pending, then this
93+
will potentially wait/block forever until the promise is settled. To avoid
94+
this, API authors creating promises are expected to provide means to
95+
configure a timeout for the promise instead. For more details, see also the
96+
[`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
97+
98+
If the deprecated `$timeout` argument is given and the promise is still pending once the
99+
timeout triggers, this will `cancel()` the promise and throw a `TimeoutException`.
100+
This implies that if you pass a really small (or negative) value, it will still
101+
start a timer and will thus trigger at the earliest possible time in the future.
102+
103+
Note that this function will assume control over the event loop. Internally, it
104+
will actually `run()` the loop until the promise settles and then calls `stop()` to
105+
terminate execution of the loop. This means this function is more suited for
106+
short-lived promise executions when using promise-based APIs is not feasible.
107+
For long-running applications, using promise-based APIs by leveraging chained
108+
`then()` calls is usually preferable.
109+
54110
### parallel()
55111

56112
The `parallel(array<callable():PromiseInterface<mixed,Exception>> $tasks): PromiseInterface<array<mixed>,Exception>` function can be used

composer.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@
2727
],
2828
"require": {
2929
"php": ">=5.3.2",
30-
"react/promise": "^2.8 || ^1.2.1"
30+
"react/event-loop": "^1.2",
31+
"react/promise": "^2.8 || ^1.2.1",
32+
"react/promise-timer": "^1.5"
3133
},
3234
"require-dev": {
33-
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
34-
"react/event-loop": "^1.2"
35+
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35"
3536
},
3637
"suggest": {
3738
"react/event-loop": "You need an event loop for this to make sense."

src/functions.php

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,124 @@
22

33
namespace React\Async;
44

5+
use React\EventLoop\Loop;
6+
use React\EventLoop\LoopInterface;
57
use React\Promise\Deferred;
68
use React\Promise\PromiseInterface;
9+
use React\Promise\Timer;
10+
11+
/**
12+
* Block waiting for the given `$promise` to be fulfilled.
13+
*
14+
* ```php
15+
* $result = React\Async\await($promise, $loop);
16+
* ```
17+
*
18+
* This function will only return after the given `$promise` has settled, i.e.
19+
* either fulfilled or rejected. In the meantime, the event loop will run any
20+
* events attached to the same loop until the promise settles.
21+
*
22+
* Once the promise is fulfilled, this function will return whatever the promise
23+
* resolved to.
24+
*
25+
* Once the promise is rejected, this will throw whatever the promise rejected
26+
* with. If the promise did not reject with an `Exception`, then this function
27+
* will throw an `UnexpectedValueException` instead.
28+
*
29+
* ```php
30+
* try {
31+
* $result = React\Async\await($promise, $loop);
32+
* // promise successfully fulfilled with $result
33+
* echo 'Result: ' . $result;
34+
* } catch (Exception $exception) {
35+
* // promise rejected with $exception
36+
* echo 'ERROR: ' . $exception->getMessage();
37+
* }
38+
* ```
39+
*
40+
* This function takes an optional `LoopInterface|null $loop` parameter that can be used to
41+
* pass the event loop instance to use. You can use a `null` value here in order to
42+
* use the [default loop](https://github.com/reactphp/event-loop#loop). This value
43+
* SHOULD NOT be given unless you're sure you want to explicitly use a given event
44+
* loop instance.
45+
*
46+
* If no `$timeout` argument is given and the promise stays pending, then this
47+
* will potentially wait/block forever until the promise is settled. To avoid
48+
* this, API authors creating promises are expected to provide means to
49+
* configure a timeout for the promise instead. For more details, see also the
50+
* [`timeout()` function](https://github.com/reactphp/promise-timer#timeout).
51+
*
52+
* If the deprecated `$timeout` argument is given and the promise is still pending once the
53+
* timeout triggers, this will `cancel()` the promise and throw a `TimeoutException`.
54+
* This implies that if you pass a really small (or negative) value, it will still
55+
* start a timer and will thus trigger at the earliest possible time in the future.
56+
*
57+
* Note that this function will assume control over the event loop. Internally, it
58+
* will actually `run()` the loop until the promise settles and then calls `stop()` to
59+
* terminate execution of the loop. This means this function is more suited for
60+
* short-lived promise executions when using promise-based APIs is not feasible.
61+
* For long-running applications, using promise-based APIs by leveraging chained
62+
* `then()` calls is usually preferable.
63+
*
64+
* @param PromiseInterface $promise
65+
* @param ?LoopInterface $loop
66+
* @param ?float $timeout [deprecated] (optional) maximum timeout in seconds or null=wait forever
67+
* @return mixed returns whatever the promise resolves to
68+
* @throws \Exception when the promise is rejected
69+
* @throws \React\Promise\Timer\TimeoutException if the $timeout is given and triggers
70+
*/
71+
function await(PromiseInterface $promise, LoopInterface $loop = null, $timeout = null)
72+
{
73+
$wait = true;
74+
$resolved = null;
75+
$exception = null;
76+
$rejected = false;
77+
$loop = $loop ?: Loop::get();
78+
79+
if ($timeout !== null) {
80+
$promise = Timer\timeout($promise, $timeout, $loop);
81+
}
82+
83+
$promise->then(
84+
function ($c) use (&$resolved, &$wait, $loop) {
85+
$resolved = $c;
86+
$wait = false;
87+
$loop->stop();
88+
},
89+
function ($error) use (&$exception, &$rejected, &$wait, $loop) {
90+
$exception = $error;
91+
$rejected = true;
92+
$wait = false;
93+
$loop->stop();
94+
}
95+
);
96+
97+
// Explicitly overwrite argument with null value. This ensure that this
98+
// argument does not show up in the stack trace in PHP 7+ only.
99+
$promise = null;
100+
101+
while ($wait) {
102+
$loop->run();
103+
}
104+
105+
if ($rejected) {
106+
if (!$exception instanceof \Exception && !$exception instanceof \Throwable) {
107+
$exception = new \UnexpectedValueException(
108+
'Promise rejected with unexpected value of type ' . (is_object($exception) ? get_class($exception) : gettype($exception))
109+
);
110+
} elseif (!$exception instanceof \Exception) {
111+
$exception = new \UnexpectedValueException(
112+
'Promise rejected with unexpected ' . get_class($exception) . ': ' . $exception->getMessage(),
113+
$exception->getCode(),
114+
$exception
115+
);
116+
}
117+
118+
throw $exception;
119+
}
120+
121+
return $resolved;
122+
}
7123

8124
/**
9125
* @param array<callable():PromiseInterface<mixed,Exception>> $tasks

0 commit comments

Comments
 (0)