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