friendship ended with social-app. php is my new best friend
1---
2title: Asynchronous Requests
3description: Learn how to make asynchronous HTTP requests with the Fetch HTTP package
4---
5
6# Asynchronous Requests
7
8This guide explains how to work with asynchronous HTTP requests in the Fetch HTTP package. Asynchronous requests allow you to execute multiple HTTP operations in parallel, which can significantly improve performance when making multiple independent requests.
9
10## Understanding Async/Await in PHP
11
12Fetch PHP brings JavaScript-like async/await patterns to PHP through the Matrix library, which is integrated into the package. While PHP doesn't have native language support for async/await, the package provides functions that enable similar functionality.
13
14The key functions for async operations are:
15
16- `async()` - Wraps a function to run asynchronously, returning a Promise
17- `await()` - Waits for a Promise to resolve and returns its value
18- `all()` - Runs multiple Promises concurrently and waits for all to complete
19- `race()` - Runs multiple Promises concurrently and returns the first to complete
20- `any()` - Returns the first Promise to successfully resolve
21- `map()` - Processes an array of items with controlled concurrency
22- `batch()` - Processes items in batches with controlled concurrency
23- `retry()` - Retries an async operation with exponential backoff
24
25## Making Asynchronous Requests
26
27To make asynchronous requests, wrap your `fetch()` calls with the `async()` function:
28
29```php
30// Import the async functions
31use function async;
32use function await;
33use function all;
34
35// Create a promise for an async request
36$promise = async(function() {
37 return fetch('https://api.example.com/users');
38});
39
40// Wait for the promise to resolve
41$response = await($promise);
42$users = $response->json();
43```
44
45## Multiple Concurrent Requests
46
47One of the main benefits of async requests is the ability to execute multiple HTTP requests in parallel:
48
49```php
50// Execute an async function
51await(async(function() {
52 // Create multiple requests
53 $results = await(all([
54 'users' => async(fn() => fetch('https://api.example.com/users')),
55 'posts' => async(fn() => fetch('https://api.example.com/posts')),
56 'comments' => async(fn() => fetch('https://api.example.com/comments'))
57 ]));
58
59 // Process the results
60 $users = $results['users']->json();
61 $posts = $results['posts']->json();
62 $comments = $results['comments']->json();
63
64 echo "Fetched " . count($users) . " users, " .
65 count($posts) . " posts, and " .
66 count($comments) . " comments";
67}));
68```
69
70## Traditional Promise-based Pattern
71
72In addition to the async/await pattern, you can use the more traditional promise-based approach:
73
74```php
75// Get the handler for async operations
76$handler = fetch_client()->getHandler();
77$handler->async();
78
79// Make the async request
80$promise = $handler->get('https://api.example.com/users');
81
82// Handle the result with callbacks
83$promise->then(
84 function ($response) {
85 // Process successful response
86 $users = $response->json();
87 foreach ($users as $user) {
88 echo $user['name'] . PHP_EOL;
89 }
90 },
91 function ($exception) {
92 // Handle errors
93 echo "Error: " . $exception->getMessage();
94 }
95);
96```
97
98## Promise Chaining
99
100You can chain operations to be executed when a promise resolves:
101
102```php
103async(function() {
104 return fetch('https://api.example.com/users');
105})
106->then(function($response) {
107 // This runs when the request succeeds
108 $users = $response->json();
109 echo "Fetched " . count($users) . " users";
110 return $users;
111})
112->then(function($users) {
113 // Process the users
114 return array_map(function($user) {
115 return $user['name'];
116 }, $users);
117})
118->then(function($userNames) {
119 echo "User names: " . implode(', ', $userNames);
120});
121```
122
123## Error Handling
124
125Handle errors in asynchronous code with try/catch in an async function or the `catch()` method:
126
127```php
128// Using try/catch with await
129await(async(function() {
130 try {
131 $response = await(async(function() {
132 return fetch('https://api.example.com/users/999');
133 }));
134
135 if ($response->isNotFound()) {
136 throw new \Exception("User not found");
137 }
138
139 return $response->json();
140 } catch (\Exception $e) {
141 echo "Error: " . $e->getMessage();
142 return [];
143 }
144}));
145
146// Using catch() with promises
147$handler = fetch_client()->getHandler();
148$handler->async()
149 ->get('https://api.example.com/users/999')
150 ->then(function($response) {
151 if ($response->failed()) {
152 throw new \Exception("API returned error: " . $response->status());
153 }
154 return $response->json();
155 })
156 ->catch(function($error) {
157 echo "Error: " . $error->getMessage();
158 return [];
159 });
160```
161
162## Using `race()` to Get the First Result
163
164Sometimes you may want whichever request finishes first:
165
166```php
167use function race;
168
169// Create promises for redundant endpoints
170$promises = [
171 async(fn() => fetch('https://api1.example.com/data')),
172 async(fn() => fetch('https://api2.example.com/data')),
173 async(fn() => fetch('https://api3.example.com/data'))
174];
175
176// Get the result from whichever completes first
177$response = await(race($promises));
178$data = $response->json();
179echo "Got data from the fastest source";
180```
181
182## Using `any()` to Get the First Success
183
184To get the first successful result (ignoring failures):
185
186```php
187use function any;
188
189// Create promises for redundant endpoints
190$promises = [
191 async(fn() => fetch('https://api1.example.com/data')),
192 async(fn() => fetch('https://api2.example.com/data')),
193 async(fn() => fetch('https://api3.example.com/data'))
194];
195
196// Get the first successful result
197try {
198 $response = await(any($promises));
199 $data = $response->json();
200 echo "Got data from the first successful source";
201} catch (\Exception $e) {
202 echo "All requests failed";
203}
204```
205
206## Controlled Concurrency with `map()`
207
208For processing many items with controlled parallelism:
209
210```php
211use function map;
212
213// List of user IDs to fetch
214$userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
215
216// Process at most 3 requests at a time
217$responses = await(map($userIds, function($id) {
218 return async(function() use ($id) {
219 return fetch("https://api.example.com/users/{$id}");
220 });
221}, 3));
222
223// Process the responses
224foreach ($responses as $index => $response) {
225 $user = $response->json();
226 echo "Processed user {$user['name']}\n";
227}
228```
229
230## Batch Processing
231
232For processing items in batches with controlled concurrency:
233
234```php
235use function batch;
236
237// Array of items to process
238$items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
239
240// Process in batches of 3 with max 2 concurrent batches
241$results = await(batch(
242 $items,
243 function($batch) {
244 // Process a batch
245 return async(function() use ($batch) {
246 $batchResults = [];
247 foreach ($batch as $id) {
248 $response = await(async(fn() =>
249 fetch("https://api.example.com/users/{$id}")
250 ));
251 $batchResults[] = $response->json();
252 }
253 return $batchResults;
254 });
255 },
256 3, // batch size
257 2 // concurrency
258));
259```
260
261## Sequential Async Requests
262
263Sometimes you need to execute requests in sequence, where each depends on the previous:
264
265```php
266await(async(function() {
267 // First request: get auth token
268 $authResponse = await(async(fn() =>
269 fetch('https://api.example.com/auth/login', [
270 'method' => 'POST',
271 'json' => [
272 'username' => 'user',
273 'password' => 'pass'
274 ]
275 ])
276 ));
277
278 $token = $authResponse->json()['token'];
279
280 // Second request: use token to get user profile
281 $profileResponse = await(async(fn() =>
282 fetch('https://api.example.com/me', [
283 'token' => $token
284 ])
285 ));
286
287 $user = $profileResponse->json();
288
289 // Third request: get user's posts
290 $postsResponse = await(async(fn() =>
291 fetch("https://api.example.com/users/{$user['id']}/posts", [
292 'token' => $token
293 ])
294 ));
295
296 return $postsResponse->json();
297}));
298```
299
300## Retries with Async
301
302You can combine async operations with retry logic for more resilient requests:
303
304```php
305use function retry;
306
307// Retry a flaky request up to 3 times with exponential backoff
308$data = await(retry(
309 function() {
310 return async(function() {
311 return fetch('https://api.example.com/unstable-endpoint');
312 });
313 },
314 3, // max attempts
315 function($attempt) {
316 // Exponential backoff strategy
317 return min(pow(2, $attempt) * 100, 1000);
318 }
319));
320```
321
322## Timeouts with async/await
323
324You can set a timeout when waiting for a promise:
325
326```php
327use function timeout;
328
329try {
330 // Add a 5-second timeout to a request
331 $response = await(timeout(
332 async(fn() => fetch('https://api.example.com/slow-endpoint')),
333 5.0
334 ));
335
336 $data = $response->json();
337} catch (\Matrix\Exceptions\TimeoutException $e) {
338 echo "Timeout error: " . $e->getMessage();
339}
340```
341
342## Using the Handler's Promise Utilities
343
344The `ClientHandler` class provides methods for working with promises:
345
346```php
347$handler = fetch_client()->getHandler();
348
349// Execute multiple promises concurrently
350$promises = [
351 async(fn() => fetch('https://api.example.com/users')),
352 async(fn() => fetch('https://api.example.com/posts'))
353];
354
355// Using the handler's promise utilities
356$results = $handler->awaitPromise($handler->all($promises));
357
358// The handler can also create resolved or rejected promises
359$resolved = $handler->resolve(['name' => 'John']);
360$rejected = $handler->reject(new \Exception('Something went wrong'));
361```
362
363## Real-World Examples
364
365### Fetching Related Resources
366
367```php
368await(async(function() {
369 // Get a user and their related data in parallel
370 $userId = 123;
371
372 // First, get the user
373 $userResponse = await(async(function() use ($userId) {
374 return fetch("https://api.example.com/users/{$userId}");
375 }));
376
377 $user = $userResponse->json();
378
379 // Then fetch posts and followers in parallel
380 $related = await(all([
381 'posts' => async(fn() => fetch("https://api.example.com/users/{$user['id']}/posts")),
382 'followers' => async(fn() => fetch("https://api.example.com/users/{$user['id']}/followers"))
383 ]));
384
385 $data = [
386 'user' => $user,
387 'posts' => $related['posts']->json(),
388 'followers' => $related['followers']->json()
389 ];
390
391 echo "User: {$data['user']['name']}\n";
392 echo "Posts: " . count($data['posts']) . "\n";
393 echo "Followers: " . count($data['followers']) . "\n";
394
395 return $data;
396}));
397```
398
399### Implementing Pagination with Async
400
401```php
402await(async(function() {
403 $url = 'https://api.example.com/users';
404 $perPage = 100;
405 $allItems = [];
406
407 // Get the first page
408 $firstPageResponse = await(async(function() use ($url, $perPage) {
409 return fetch("{$url}?per_page={$perPage}&page=1");
410 }));
411
412 $firstPageData = $firstPageResponse->json();
413 $items = $firstPageData['items'] ?? $firstPageData;
414 $allItems = array_merge($allItems, $items);
415
416 $totalCount = $firstPageResponse->header('X-Total-Count')
417 ? (int)$firstPageResponse->header('X-Total-Count')
418 : count($items);
419 $totalPages = ceil($totalCount / $perPage);
420
421 // If we have multiple pages, fetch them all in parallel
422 if ($totalPages > 1) {
423 $pagePromises = [];
424
425 for ($page = 2; $page <= $totalPages; $page++) {
426 $pagePromises[] = async(function() use ($url, $perPage, $page) {
427 return fetch("{$url}?per_page={$perPage}&page={$page}");
428 });
429 }
430
431 // Wait for all pages
432 $pageResponses = await(all($pagePromises));
433
434 // Process all responses
435 foreach ($pageResponses as $response) {
436 $pageData = $response->json();
437 $pageItems = $pageData['items'] ?? $pageData;
438 $allItems = array_merge($allItems, $pageItems);
439 }
440 }
441
442 return $allItems;
443}));
444```
445
446## Best Practices for Async Requests
447
4481. **Use Async for Multiple Requests**: Asynchronous requests are most beneficial when making multiple independent HTTP requests.
449
4502. **Control Concurrency**: Don't create too many concurrent requests. Use `map()` or `batch()` with a reasonable concurrency limit.
451
4523. **Handle All Errors**: Always include error handling with try/catch or `.catch()` for async operations.
453
4544. **Keep Functions Pure**: Avoid side effects in async functions for better predictability.
455
4565. **Combine with Retries**: Use the `retry()` function for more resilient async operations.
457
4586. **Set Appropriate Timeouts**: Use `timeout()` to prevent operations from hanging indefinitely.
459
4607. **Avoid Mixing Sync and Async**: When using async, make all your HTTP operations async for consistent code patterns.
461
4628. **Be Mindful of Server Load**: While async lets you make many requests in parallel, be mindful of rate limits and server capacity.
463
464## Next Steps
465
466- Learn about [Promise Operations](/guide/promise-operations) for more advanced async patterns
467- Explore [Error Handling](/guide/error-handling) for handling errors in async code
468- See [Retry Handling](/guide/retry-handling) for making async requests more resilient