Skip to content

Commit 32483f4

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

File tree

2 files changed

+327
-17
lines changed

2 files changed

+327
-17
lines changed

README.md

Lines changed: 166 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Async
1+
# Async Utilities
22

33
[![CI status](https://github.com/reactphp/async/workflows/CI/badge.svg)](https://github.com/reactphp/async/actions)
44

@@ -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,146 @@ 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+
as shown in the previous example.
106+
107+
The `async()` function is specifically designed for cases where it is used
108+
as a callback (such as an event loop timer, event listener, or promise
109+
callback). For this reason, it returns a new function wrapping the given
110+
`$function` instead of directly invoking it and returning its value.
111+
112+
```php
113+
use function React\Async\async;
114+
115+
Loop::addTimer(1.0, async(function () { … }));
116+
$connection->on('close', async(function () { … }));
117+
$stream->on('data', async(function ($data) { … }));
118+
$promise->then(async(function (int $result) { … }));
119+
```
120+
121+
You can invoke this wrapping function to invoke the given `$function` with
122+
any arguments given as-is. The function will always return a Promise which
123+
will be fulfilled with whatever your `$function` returns. Likewise, it will
124+
return a promise that will be rejected if you throw an `Exception` or
125+
`Throwable` from your `$function`. This allows you to easily create
126+
Promise-based functions:
127+
128+
```php
129+
$promise = React\Async\async(function (): int {
130+
$browser = new React\Http\Browser();
131+
$urls = [
132+
'https://example.com/alice',
133+
'https://example.com/bob'
134+
];
135+
136+
$bytes = 0;
137+
foreach ($urls as $url) {
138+
$response = React\Async\await($browser->get($url));
139+
assert($response instanceof Psr\Http\Message\ResponseInterface);
140+
$bytes += $response->getBody()->getSize();
141+
}
142+
return $bytes;
143+
})();
144+
145+
$promise->then(function (int $bytes) {
146+
echo 'Total size: ' . $bytes . PHP_EOL;
147+
}, function (Exception $e) {
148+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
149+
});
150+
```
151+
152+
The previous example uses [`await()`](#await) inside a loop to highlight how
153+
this vastly simplifies consuming asynchronous operations. At the same time,
154+
this naive example does not leverage concurrent execution, as it will
155+
essentially "await" between each operation. In order to take advantage of
156+
concurrent execution within the given `$function`, you can "await" multiple
157+
promises by using a single [`await()`](#await) together with Promise-based
158+
primitives like this:
159+
160+
```php
161+
$promise = React\Async\async(function (): int {
162+
$browser = new React\Http\Browser();
163+
$urls = [
164+
'https://example.com/alice',
165+
'https://example.com/bob'
166+
];
167+
168+
$promises = [];
169+
foreach ($urls as $url) {
170+
$promises[] = $browser->get($url);
171+
}
172+
173+
try {
174+
$responses = React\Async\await(React\Promise\all($promises));
175+
} catch (Exception $e) {
176+
foreach ($promises as $promise) {
177+
$promise->cancel();
178+
}
179+
throw $e;
180+
}
181+
182+
$bytes = 0;
183+
foreach ($responses as $response) {
184+
assert($response instanceof Psr\Http\Message\ResponseInterface);
185+
$bytes += $response->getBody()->getSize();
186+
}
187+
return $bytes;
188+
})();
189+
190+
$promise->then(function (int $bytes) {
191+
echo 'Total size: ' . $bytes . PHP_EOL;
192+
}, function (Exception $e) {
193+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
194+
});
195+
```
196+
56197
### await()
57198

58199
The `await(PromiseInterface $promise): mixed` function can be used to
@@ -63,8 +204,27 @@ $result = React\Async\await($promise);
63204
```
64205

65206
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.
207+
either fulfilled or rejected. While the promise is pending, this function
208+
can be considered *blocking* from the perspective of the calling code.
209+
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
210+
call. Everything inside this function will still be blocked, but everything
211+
outside this function can be executed asynchronously without blocking:
212+
213+
```php
214+
Loop::addTimer(0.5, React\Async\async(function() {
215+
echo 'a';
216+
React\async\await(React\Promise\Timer\sleep(1.0));
217+
echo 'c';
218+
}));
219+
220+
Loop::addTimer(1.0, fn() => echo 'b');
221+
222+
// prints "a" at t=0.5s
223+
// prints "b" at t=1.0s
224+
// prints "c" at t=1.5s
225+
```
226+
227+
See also the [`async()` function](#async) for more details.
68228

69229
Once the promise is fulfilled, this function will return whatever the promise
70230
resolved to.
@@ -125,10 +285,11 @@ when the promise is fulfilled. The `yield` statement returns whatever the
125285
promise is fulfilled with. If the promise is rejected, it will throw an
126286
`Exception` or `Throwable`.
127287

128-
The `coroutine()` function will always return a Proimise which will be
288+
The `coroutine()` function will always return a Promise which will be
129289
fulfilled with whatever your `$function` returns. Likewise, it will return
130290
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:
291+
from your `$function`. This allows you to easily create Promise-based
292+
functions:
132293

133294
```php
134295
$promise = React\Async\coroutine(function () {

0 commit comments

Comments
 (0)