friendship ended with social-app. php is my new best friend
1--- 2title: Promise Operations 3description: Learn how to work with promises in the Fetch HTTP package 4--- 5 6# Promise Operations 7 8This guide covers how to work with promises in the Fetch HTTP package. The package implements a Promise-based API similar to JavaScript promises, allowing for sophisticated asynchronous programming patterns. 9 10## Basic Promise Concepts 11 12Promises represent values that may not be available yet. They're used for asynchronous operations like HTTP requests. In the Fetch HTTP package, promises are represented by the `PromiseInterface` from React's Promise library. 13 14## Creating Promises 15 16There are several ways to create promises: 17 18```php 19// Create a promise from an async function 20$promise = async(function() { 21 return fetch('https://api.example.com/users'); 22}); 23 24// Create a resolved promise with a value 25$resolvedPromise = resolve(['name' => 'John', 'email' => 'john@example.com']); 26 27// Through the ClientHandler 28$handler = fetch_client()->getHandler(); 29$resolvedPromise = $handler->resolve(['name' => 'John']); 30 31// Create a rejected promise with an error 32$rejectedPromise = reject(new \Exception('Something went wrong')); 33 34// Through the ClientHandler 35$handler = fetch_client()->getHandler(); 36$rejectedPromise = $handler->reject(new \Exception('Something went wrong')); 37``` 38 39## Promise Methods 40 41### then() 42 43The `then()` method registers callbacks for when a promise resolves successfully or fails: 44 45```php 46$promise = async(function() { 47 return fetch('https://api.example.com/users'); 48}); 49 50$promise->then( 51 function ($response) { 52 // Success callback 53 $users = $response->json(); 54 echo "Fetched " . count($users) . " users"; 55 return $users; 56 }, 57 function ($error) { 58 // Error callback 59 echo "Error: " . $error->getMessage(); 60 } 61); 62``` 63 64The `then()` method returns a new promise that resolves with the return value of the callback. 65 66### catch() 67 68The `catch()` method is a shorthand for handling errors: 69 70```php 71$promise = async(function() { 72 return fetch('https://api.example.com/users'); 73}); 74 75$promise 76 ->then(function ($response) { 77 $users = $response->json(); 78 echo "Fetched " . count($users) . " users"; 79 return $users; 80 }) 81 ->catch(function ($error) { 82 echo "Error: " . $error->getMessage(); 83 }); 84``` 85 86### finally() 87 88The `finally()` method registers a callback that runs when the promise settles, regardless of whether it was resolved or rejected: 89 90```php 91$promise = async(function() { 92 return fetch('https://api.example.com/users'); 93}); 94 95$promise 96 ->then(function ($response) { 97 $users = $response->json(); 98 echo "Fetched " . count($users) . " users"; 99 }) 100 ->catch(function ($error) { 101 echo "Error: " . $error->getMessage(); 102 }) 103 ->finally(function () { 104 echo "Request completed."; 105 }); 106``` 107 108## Using with ClientHandler 109 110The `ClientHandler` provides methods for working with promises: 111 112```php 113// Get the handler from the client 114$handler = fetch_client()->getHandler(); 115 116// Enable async mode 117$handler->async(); 118 119// Make an async request 120$promise = $handler->get('https://api.example.com/users'); 121 122// Add callbacks 123$promise->then( 124 function ($response) { 125 $users = $response->json(); 126 return $users; 127 } 128)->catch( 129 function ($error) { 130 echo "Error: " . $error->getMessage(); 131 } 132); 133 134// Wait for a promise to resolve 135$result = $handler->awaitPromise($promise); 136``` 137 138## Combining Multiple Promises 139 140The package provides several functions for working with multiple promises: 141 142### all() 143 144The `all()` function waits for all promises to resolve, or rejects if any promise fails: 145 146```php 147// Create multiple promises 148$usersPromise = async(function() { 149 return fetch('https://api.example.com/users'); 150}); 151 152$postsPromise = async(function() { 153 return fetch('https://api.example.com/posts'); 154}); 155 156$commentsPromise = async(function() { 157 return fetch('https://api.example.com/comments'); 158}); 159 160// Wait for all to complete 161all([ 162 'users' => $usersPromise, 163 'posts' => $postsPromise, 164 'comments' => $commentsPromise 165])->then(function ($results) { 166 // $results is an array with keys 'users', 'posts', 'comments' 167 $users = $results['users']->json(); 168 $posts = $results['posts']->json(); 169 $comments = $results['comments']->json(); 170 171 echo "Fetched " . count($users) . " users, " . 172 count($posts) . " posts, and " . 173 count($comments) . " comments"; 174}); 175 176// Using the handler 177$handler = fetch_client()->getHandler(); 178$results = $handler->awaitPromise($handler->all([ 179 'users' => $usersPromise, 180 'posts' => $postsPromise 181])); 182``` 183 184If you use numeric keys, the results will be returned in the same order: 185 186```php 187all([ 188 $usersPromise, 189 $postsPromise, 190 $commentsPromise 191])->then(function ($results) { 192 // $results is an indexed array 193 $users = $results[0]->json(); 194 $posts = $results[1]->json(); 195 $comments = $results[2]->json(); 196}); 197``` 198 199### race() 200 201The `race()` function waits for the first promise to settle (resolve or reject): 202 203```php 204// Create promises for redundant endpoints 205$promises = [ 206 async(fn() => fetch('https://api1.example.com/data')), 207 async(fn() => fetch('https://api2.example.com/data')), 208 async(fn() => fetch('https://api3.example.com/data')) 209]; 210 211// Get the result from whichever completes first (success or failure) 212race($promises) 213 ->then(function ($response) { 214 $data = $response->json(); 215 echo "Got data from the fastest source"; 216 }); 217 218// Using the handler 219$handler = fetch_client()->getHandler(); 220$result = $handler->awaitPromise($handler->race($promises)); 221``` 222 223### any() 224 225The `any()` function waits for the first promise to resolve, ignoring rejections unless all promises reject: 226 227```php 228// Create promises with some that might fail 229$promises = [ 230 async(fn() => fetch('https://api1.example.com/data')), // Might fail 231 async(fn() => fetch('https://api2.example.com/data')), // Might fail 232 async(fn() => fetch('https://api3.example.com/data')) 233]; 234 235// Get the first successful result 236any($promises) 237 ->then(function ($response) { 238 $data = $response->json(); 239 echo "Got data from the first successful source"; 240 }) 241 ->catch(function ($errors) { 242 echo "All requests failed!"; 243 }); 244 245// Using the handler 246$handler = fetch_client()->getHandler(); 247try { 248 $result = $handler->awaitPromise($handler->any($promises)); 249} catch (\Exception $e) { 250 echo "All requests failed!"; 251} 252``` 253 254### Using await() with Promise Combinators 255 256You can also use `await()` with the promise combinators for a more synchronous-looking code: 257 258```php 259await(async(function() { 260 // Wait for multiple promises with all() 261 $results = await(all([ 262 'users' => async(fn() => fetch('https://api.example.com/users')), 263 'posts' => async(fn() => fetch('https://api.example.com/posts')) 264 ])); 265 266 $users = $results['users']->json(); 267 $posts = $results['posts']->json(); 268 269 echo "Fetched " . count($users) . " users and " . count($posts) . " posts"; 270})); 271``` 272 273## Sequential Operations 274 275You can perform sequential asynchronous operations using `await()`: 276 277```php 278await(async(function() { 279 // First request: get auth token 280 $authResponse = await(async(fn() => 281 fetch('https://api.example.com/auth/login', [ 282 'method' => 'POST', 283 'json' => [ 284 'username' => 'user', 285 'password' => 'pass' 286 ] 287 ]) 288 )); 289 290 $token = $authResponse->json()['token']; 291 292 // Second request: use token to get user data 293 $userResponse = await(async(fn() => 294 fetch('https://api.example.com/me', [ 295 'token' => $token 296 ]) 297 )); 298 299 return $userResponse->json(); 300})); 301``` 302 303## Controlled Concurrency with map() 304 305The `map()` function applies an async function to each item in an array with controlled concurrency: 306 307```php 308use function map; 309 310// List of user IDs 311$userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 312 313// Fetch user details for each ID, with at most 3 concurrent requests 314$responses = await(map($userIds, function ($userId) { 315 return async(function() use ($userId) { 316 return fetch("https://api.example.com/users/{$userId}"); 317 }); 318}, 3)); 319 320$users = []; 321foreach ($responses as $response) { 322 $users[] = $response->json(); 323} 324 325echo "Fetched details for " . count($users) . " users"; 326``` 327 328You can also use the handler's map method: 329 330```php 331$handler = fetch_client()->getHandler(); 332$responses = $handler->awaitPromise($handler->map($userIds, function($id) use ($handler) { 333 return $handler->wrapAsync(function() use ($id) { 334 return fetch("https://api.example.com/users/{$id}"); 335 }); 336}, 3)); 337``` 338 339## Batch Processing 340 341For processing items in batches rather than one at a time: 342 343```php 344use function batch; 345 346// List of user IDs 347$userIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 348 349// Process in batches of 5 with max 2 concurrent batches 350$results = await(batch( 351 $userIds, 352 function ($batchOfIds) { 353 return async(function() use ($batchOfIds) { 354 $queryString = implode(',', $batchOfIds); 355 return fetch("https://api.example.com/users?ids={$queryString}"); 356 }); 357 }, 358 5, // Batch size 359 2 // Concurrency 360)); 361 362// Process results from each batch 363foreach ($results as $response) { 364 $batchUsers = $response->json(); 365 echo "Processed batch with " . count($batchUsers) . " users\n"; 366} 367``` 368 369## Timeout Handling 370 371You can add timeouts to promises: 372 373```php 374use function timeout; 375 376try { 377 // Add a 5-second timeout to a request 378 $response = await(timeout( 379 async(fn() => fetch('https://api.example.com/slow-endpoint')), 380 5.0 381 )); 382 383 $data = $response->json(); 384} catch (\Matrix\Exceptions\TimeoutException $e) { 385 echo "Timeout occurred: " . $e->getMessage(); 386} 387 388// Or using the handler 389$handler = fetch_client()->getHandler(); 390try { 391 $response = $handler->awaitPromise($promise, 5.0); // 5 second timeout 392} catch (\RuntimeException $e) { 393 echo "Timeout: " . $e->getMessage(); 394} 395``` 396 397## Retry Handling 398 399For operations that might fail, you can use the `retry()` function: 400 401```php 402use function retry; 403 404$result = await(retry( 405 function() { 406 return async(function() { 407 return fetch('https://api.example.com/unstable-endpoint'); 408 }); 409 }, 410 3, // Max attempts 411 function ($attempt) { 412 // Exponential backoff strategy 413 return min(pow(2, $attempt) * 100, 1000); 414 } 415)); 416 417// Process the successful response 418$data = $result->json(); 419``` 420 421## Advanced Promise Patterns 422 423### Promise Chaining 424 425You can chain promises to transform values or perform sequential operations: 426 427```php 428async(function() { 429 return fetch('https://api.example.com/users'); 430}) 431->then(function ($response) { 432 return $response->json(); 433}) 434->then(function ($users) { 435 // Filter users 436 return array_filter($users, function ($user) { 437 return $user['active'] === true; 438 }); 439}) 440->then(function ($activeUsers) { 441 // Extract emails 442 return array_map(function ($user) { 443 return $user['email']; 444 }, $activeUsers); 445}) 446->then(function ($emails) { 447 echo "Active user emails: " . implode(', ', $emails); 448}); 449``` 450 451### Error Handling with try/catch 452 453Using `await()` allows for traditional try/catch error handling: 454 455```php 456await(async(function() { 457 try { 458 $response = await(async(function() { 459 return fetch('https://api.example.com/users'); 460 })); 461 462 if ($response->failed()) { 463 throw new \Exception("API error: " . $response->status()); 464 } 465 466 $users = $response->json(); 467 468 if (empty($users)) { 469 throw new \Exception("No users found"); 470 } 471 472 return $users[0]['name']; 473 } catch (\Exception $e) { 474 echo "Error: " . $e->getMessage(); 475 return "Unknown user"; 476 } 477})); 478``` 479 480### Dynamic Promise Creation 481 482You can create promises dynamically based on previous results: 483 484```php 485await(async(function() { 486 // Get all users 487 $usersResponse = await(async(function() { 488 return fetch('https://api.example.com/users'); 489 })); 490 491 $users = $usersResponse->json(); 492 493 // Create an array of promises for each user's posts 494 $promises = []; 495 foreach ($users as $user) { 496 $userId = $user['id']; 497 $promises[$userId] = async(function() use ($userId) { 498 return fetch("https://api.example.com/users/{$userId}/posts"); 499 }); 500 } 501 502 // Execute all promises concurrently 503 $postResponses = await(all($promises)); 504 505 // Process the results 506 $userPosts = []; 507 foreach ($postResponses as $userId => $response) { 508 $userPosts[$userId] = $response->json(); 509 } 510 511 return $userPosts; 512})); 513``` 514 515## Working with Specific Response Methods 516 517The `Response` class in Fetch PHP provides many helpful methods that work well with promises: 518 519```php 520await(async(function() { 521 $response = await(async(fn() => fetch('https://api.example.com/users/1'))); 522 523 // Check if successful 524 if ($response->successful()) { 525 $user = $response->json(); 526 echo "User: {$user['name']}\n"; 527 528 // Check specific properties 529 if ($response->hasJsonContent()) { 530 // Content is JSON 531 } 532 533 // Check status codes 534 if ($response->isOk()) { 535 // Status is 200 OK 536 } else if ($response->isNotFound()) { 537 // Status is 404 Not Found 538 } 539 } 540})); 541``` 542 543## Best Practices 544 5451. **Use async/await for Readability**: The async/await pattern makes asynchronous code more readable by making it look like synchronous code. 546 547 ```php 548 // Instead of nested then() callbacks: 549 await(async(function() { 550 $response = await(async(fn() => fetch('https://api.example.com/users'))); 551 $users = $response->json(); 552 // Process users directly 553 })); 554 ``` 555 5562. **Always Handle Errors**: Use try/catch with await or catch() with promises to handle errors. 557 5583. **Avoid Nesting**: Use async/await to avoid the "callback hell" or "pyramid of doom" problem. 559 5604. **Manage Concurrency**: Use `map()` or `batch()` with reasonable concurrency limits to avoid server overload. 561 5625. **Control Timeouts**: Set appropriate timeouts with the `timeout()` function to prevent operations from hanging. 563 5646. **Use Promise Combinators**: Leverage `all()`, `race()`, and `any()` for managing multiple concurrent operations. 565 5667. **Use Type-Safe Enums with Responses**: Take advantage of response methods like `statusEnum()` for type safety. 567 568 ```php 569 use Fetch\Enum\Status; 570 571 await(async(function() { 572 $response = await(async(fn() => fetch('https://api.example.com/users'))); 573 574 if ($response->statusEnum() === Status::OK) { 575 // Status is exactly 200 OK 576 } 577 })); 578 ``` 579 5808. **Consider Memory Usage**: Be mindful of memory usage when working with large datasets. 581 582## Debugging Async/Await Code 583 584Debugging asynchronous code can be challenging. Here are some tips: 585 5861. **Break Complex Operations**: Split complex async operations into smaller steps. 587 5882. **Add Logging**: Log interim results to track the flow of execution. 589 590 ```php 591 await(async(function() { 592 echo "Fetching users...\n"; 593 $response = await(async(fn() => fetch('https://api.example.com/users'))); 594 595 echo "Processing response...\n"; 596 $users = $response->json(); 597 598 echo "Found " . count($users) . " users\n"; 599 return $users; 600 })); 601 ``` 602 6033. **Use try/catch Blocks**: Place try/catch blocks around specific operations to catch errors at their source. 604 6054. **Check Promise States**: If things aren't working as expected, check if promises are resolving or rejecting. 606 6075. **Use the Handler's Debug Method**: The ClientHandler's `debug()` method can provide useful information. 608 609 ```php 610 $handler = fetch_client()->getHandler(); 611 $debugInfo = $handler->debug(); 612 print_r($debugInfo); 613 ``` 614 615## Next Steps 616 617- Explore [Asynchronous Requests](/guide/async-requests) for practical examples 618- Learn about [Error Handling](/guide/error-handling) in asynchronous code 619- See [Retry Handling](/guide/retry-handling) for making async requests more resilient 620- Check out [Type-Safe Enums](/guide/working-with-enums) for working with HTTP concepts