Skip to content

Commit 1003a74

Browse files
committed
Add documentation for async() function and Fiber-based await()
1 parent ff11a7a commit 1003a74

File tree

2 files changed

+324
-16
lines changed

2 files changed

+324
-16
lines changed

README.md

Lines changed: 164 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+
* [async()](#async)
1920
* [await()](#await)
2021
* [coroutine()](#coroutine)
2122
* [parallel()](#parallel)
@@ -53,6 +54,145 @@ use React\Async;
5354
Async\await(…);
5455
```
5556

57+
### async()
58+
59+
The `async(callable $function): callable` function can be used to
60+
return an async function for a function that uses [`await()`](#await) internally.
61+
62+
This function is specifically designed to complement the [`await()` function](#await).
63+
The [`await()` function](#await) can be considered *blocking* from the
64+
perspective of the calling code. You can avoid this blocking behavior by
65+
wrapping it in an `async()` function call. Everything inside this function
66+
will still be blocked, but everything outside this function can be executed
67+
asynchronously without blocking:
68+
69+
```php
70+
Loop::addTimer(0.5, React\Async\async(function() {
71+
echo 'a';
72+
React\async\await(React\Promise\Timer\sleep(1.0));
73+
echo 'c';
74+
}));
75+
76+
Loop::addTimer(1.0, fn() => echo 'b'));
77+
78+
// prints "a" at t=0.5s
79+
// prints "b" at t=1.0s
80+
// prints "c" at t=1.5s
81+
```
82+
83+
See also the [`await()` function](#await) for more details.
84+
85+
Note that this function only works in tandem with the [`await()` function](#await).
86+
In particular, this function does not "magically" make any blocking function
87+
non-blocking:
88+
89+
```php
90+
Loop::addTimer(0.5, React\Async\async(function() {
91+
echo 'a';
92+
sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
93+
echo 'c';
94+
}));
95+
96+
Loop::addTimer(1.0, fn() => echo 'b'));
97+
98+
// prints "a" at t=0.5s
99+
// prints "c" at t=1.5s: Correct timing, but wrong order
100+
// prints "b" at t=1.5s: Triggered too late because it was blocked
101+
```
102+
103+
As an alternative, you should always make sure to use this function in tandem
104+
with the [`await()` function](#await) and an async API returning a promise.
105+
106+
The `async()` function is specifically designed for cases where it is used
107+
as a callback (such as an event loop timer, event listener, or promise
108+
callback). For this reason, it returns a new function wrapping the given
109+
`$function` instead of directly invoking it and returning its value.
110+
111+
```php
112+
use function React\Async\async;
113+
114+
Loop::addTimer(1.0, async(function () { … }));
115+
$connection->on('close', async(function () { … }));
116+
$stream->on('data', async(function ($data) { … }));
117+
$promise->then(async(function (int $result) { … }));
118+
```
119+
120+
You can invoke this wrapping function to invoke the given `$function` with
121+
any arguments given as-is. The function will always return a Promise which
122+
will be fulfilled with whatever your `$function` returns. Likewise, it will
123+
return a promise that will be rejected if you throw an `Exception` or
124+
`Throwable` from your `$function`. This allows you to easily create
125+
Promise-based functions:
126+
127+
```php
128+
$promise = React\Async\async(function (): int {
129+
$browser = new React\Http\Browser();
130+
$urls = [
131+
'https://example.com/alice',
132+
'https://example.com/bob'
133+
];
134+
135+
$bytes = 0;
136+
foreach ($urls as $url) {
137+
$response = React\Async\await($browser->get($url));
138+
assert($response instanceof Psr\Http\Message\ResponseInterface);
139+
$bytes += $response->getBody()->getSize();
140+
}
141+
return $bytes;
142+
})();
143+
144+
$promise->then(function (int $bytes) {
145+
echo 'Total size: ' . $bytes . PHP_EOL;
146+
}, function (Exception $e) {
147+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
148+
});
149+
```
150+
151+
The previous example uses [`await()`](#await) inside a loop to highlight how
152+
this vastly simplifies consuming asynchronous operations. At the same time,
153+
this naive example does not leverage concurrent execution, as it will
154+
essentially "await" between each operation. In order to take advantage of
155+
concurrent execution within the given `$function`, you can "await" multiple
156+
promises by using a single [`await()`](#await) together with Promise-based
157+
primitives like this:
158+
159+
```php
160+
$promise = React\Async\async(function (): int {
161+
$browser = new React\Http\Browser();
162+
$urls = [
163+
'https://example.com/alice',
164+
'https://example.com/bob'
165+
];
166+
167+
$promises = [];
168+
foreach ($urls as $url) {
169+
$promises[] = $browser->get($url);
170+
}
171+
172+
try {
173+
$responses = React\Async\await(React\Promise\all($promises));
174+
} catch (Exception $e) {
175+
foreach ($promises as $promise) {
176+
$promise->cancel();
177+
}
178+
throw $e;
179+
}
180+
181+
$bytes = 0;
182+
foreach ($responses as $response) {
183+
assert($response instanceof Psr\Http\Message\ResponseInterface);
184+
$bytes += $response->getBody()->getSize();
185+
}
186+
return $bytes;
187+
})();
188+
189+
$promise->then(function (int $bytes) {
190+
echo 'Total size: ' . $bytes . PHP_EOL;
191+
}, function (Exception $e) {
192+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
193+
});
194+
```
195+
56196
### await()
57197

58198
The `await(PromiseInterface $promise): mixed` function can be used to
@@ -63,8 +203,27 @@ $result = React\Async\await($promise);
63203
```
64204

65205
This function will only return after the given `$promise` has settled, i.e.
66-
either fulfilled or rejected. While the promise is pending, this function will
67-
suspend the fiber it's called from until the promise is settled.
206+
either fulfilled or rejected. While the promise is pending, this function
207+
can be considered *blocking* from the perspective of the calling code.
208+
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
209+
call. Everything inside this function will still be blocked, but everything
210+
outside this function can be executed asynchronously without blocking:
211+
212+
```php
213+
Loop::addTimer(0.5, React\Async\async(function() {
214+
echo 'a';
215+
React\async\await(React\Promise\Timer\sleep(1.0));
216+
echo 'c';
217+
}));
218+
219+
Loop::addTimer(1.0, fn() => echo 'b'));
220+
221+
// prints "a" at t=0.5s
222+
// prints "b" at t=1.0s
223+
// prints "c" at t=1.5s
224+
```
225+
226+
See also the [`async()` function](#async) for more details.
68227

69228
Once the promise is fulfilled, this function will return whatever the promise
70229
resolved to.
@@ -125,10 +284,11 @@ when the promise is fulfilled. The `yield` statement returns whatever the
125284
promise is fulfilled with. If the promise is rejected, it will throw an
126285
`Exception` or `Throwable`.
127286

128-
The `coroutine()` function will always return a Proimise which will be
287+
The `coroutine()` function will always return a Promise which will be
129288
fulfilled with whatever your `$function` returns. Likewise, it will return
130289
a promise that will be rejected if you throw an `Exception` or `Throwable`
131-
from your `$function`. This allows you easily create Promise-based functions:
290+
from your `$function`. This allows you to easily create Promise-based
291+
functions:
132292

133293
```php
134294
$promise = React\Async\coroutine(function () {

0 commit comments

Comments
 (0)