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