1# Guzzle Promises 2 3[Promises/A+](https://promisesaplus.com/) implementation that handles promise 4chaining and resolution iteratively, allowing for "infinite" promise chaining 5while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/) 6for a general introduction to promises. 7 8- [Features](#features) 9- [Quick start](#quick-start) 10- [Synchronous wait](#synchronous-wait) 11- [Cancellation](#cancellation) 12- [API](#api) 13 - [Promise](#promise) 14 - [FulfilledPromise](#fulfilledpromise) 15 - [RejectedPromise](#rejectedpromise) 16- [Promise interop](#promise-interop) 17- [Implementation notes](#implementation-notes) 18 19 20# Features 21 22- [Promises/A+](https://promisesaplus.com/) implementation. 23- Promise resolution and chaining is handled iteratively, allowing for 24 "infinite" promise chaining. 25- Promises have a synchronous `wait` method. 26- Promises can be cancelled. 27- Works with any object that has a `then` function. 28- C# style async/await coroutine promises using 29 `GuzzleHttp\Promise\Coroutine::of()`. 30 31 32# Quick start 33 34A *promise* represents the eventual result of an asynchronous operation. The 35primary way of interacting with a promise is through its `then` method, which 36registers callbacks to receive either a promise's eventual value or the reason 37why the promise cannot be fulfilled. 38 39 40## Callbacks 41 42Callbacks are registered with the `then` method by providing an optional 43`$onFulfilled` followed by an optional `$onRejected` function. 44 45 46```php 47use GuzzleHttp\Promise\Promise; 48 49$promise = new Promise(); 50$promise->then( 51 // $onFulfilled 52 function ($value) { 53 echo 'The promise was fulfilled.'; 54 }, 55 // $onRejected 56 function ($reason) { 57 echo 'The promise was rejected.'; 58 } 59); 60``` 61 62*Resolving* a promise means that you either fulfill a promise with a *value* or 63reject a promise with a *reason*. Resolving a promises triggers callbacks 64registered with the promises's `then` method. These callbacks are triggered 65only once and in the order in which they were added. 66 67 68## Resolving a promise 69 70Promises are fulfilled using the `resolve($value)` method. Resolving a promise 71with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger 72all of the onFulfilled callbacks (resolving a promise with a rejected promise 73will reject the promise and trigger the `$onRejected` callbacks). 74 75```php 76use GuzzleHttp\Promise\Promise; 77 78$promise = new Promise(); 79$promise 80 ->then(function ($value) { 81 // Return a value and don't break the chain 82 return "Hello, " . $value; 83 }) 84 // This then is executed after the first then and receives the value 85 // returned from the first then. 86 ->then(function ($value) { 87 echo $value; 88 }); 89 90// Resolving the promise triggers the $onFulfilled callbacks and outputs 91// "Hello, reader." 92$promise->resolve('reader.'); 93``` 94 95 96## Promise forwarding 97 98Promises can be chained one after the other. Each then in the chain is a new 99promise. The return value of a promise is what's forwarded to the next 100promise in the chain. Returning a promise in a `then` callback will cause the 101subsequent promises in the chain to only be fulfilled when the returned promise 102has been fulfilled. The next promise in the chain will be invoked with the 103resolved value of the promise. 104 105```php 106use GuzzleHttp\Promise\Promise; 107 108$promise = new Promise(); 109$nextPromise = new Promise(); 110 111$promise 112 ->then(function ($value) use ($nextPromise) { 113 echo $value; 114 return $nextPromise; 115 }) 116 ->then(function ($value) { 117 echo $value; 118 }); 119 120// Triggers the first callback and outputs "A" 121$promise->resolve('A'); 122// Triggers the second callback and outputs "B" 123$nextPromise->resolve('B'); 124``` 125 126## Promise rejection 127 128When a promise is rejected, the `$onRejected` callbacks are invoked with the 129rejection reason. 130 131```php 132use GuzzleHttp\Promise\Promise; 133 134$promise = new Promise(); 135$promise->then(null, function ($reason) { 136 echo $reason; 137}); 138 139$promise->reject('Error!'); 140// Outputs "Error!" 141``` 142 143## Rejection forwarding 144 145If an exception is thrown in an `$onRejected` callback, subsequent 146`$onRejected` callbacks are invoked with the thrown exception as the reason. 147 148```php 149use GuzzleHttp\Promise\Promise; 150 151$promise = new Promise(); 152$promise->then(null, function ($reason) { 153 throw new Exception($reason); 154})->then(null, function ($reason) { 155 assert($reason->getMessage() === 'Error!'); 156}); 157 158$promise->reject('Error!'); 159``` 160 161You can also forward a rejection down the promise chain by returning a 162`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or 163`$onRejected` callback. 164 165```php 166use GuzzleHttp\Promise\Promise; 167use GuzzleHttp\Promise\RejectedPromise; 168 169$promise = new Promise(); 170$promise->then(null, function ($reason) { 171 return new RejectedPromise($reason); 172})->then(null, function ($reason) { 173 assert($reason === 'Error!'); 174}); 175 176$promise->reject('Error!'); 177``` 178 179If an exception is not thrown in a `$onRejected` callback and the callback 180does not return a rejected promise, downstream `$onFulfilled` callbacks are 181invoked using the value returned from the `$onRejected` callback. 182 183```php 184use GuzzleHttp\Promise\Promise; 185 186$promise = new Promise(); 187$promise 188 ->then(null, function ($reason) { 189 return "It's ok"; 190 }) 191 ->then(function ($value) { 192 assert($value === "It's ok"); 193 }); 194 195$promise->reject('Error!'); 196``` 197 198# Synchronous wait 199 200You can synchronously force promises to complete using a promise's `wait` 201method. When creating a promise, you can provide a wait function that is used 202to synchronously force a promise to complete. When a wait function is invoked 203it is expected to deliver a value to the promise or reject the promise. If the 204wait function does not deliver a value, then an exception is thrown. The wait 205function provided to a promise constructor is invoked when the `wait` function 206of the promise is called. 207 208```php 209$promise = new Promise(function () use (&$promise) { 210 $promise->resolve('foo'); 211}); 212 213// Calling wait will return the value of the promise. 214echo $promise->wait(); // outputs "foo" 215``` 216 217If an exception is encountered while invoking the wait function of a promise, 218the promise is rejected with the exception and the exception is thrown. 219 220```php 221$promise = new Promise(function () use (&$promise) { 222 throw new Exception('foo'); 223}); 224 225$promise->wait(); // throws the exception. 226``` 227 228Calling `wait` on a promise that has been fulfilled will not trigger the wait 229function. It will simply return the previously resolved value. 230 231```php 232$promise = new Promise(function () { die('this is not called!'); }); 233$promise->resolve('foo'); 234echo $promise->wait(); // outputs "foo" 235``` 236 237Calling `wait` on a promise that has been rejected will throw an exception. If 238the rejection reason is an instance of `\Exception` the reason is thrown. 239Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason 240can be obtained by calling the `getReason` method of the exception. 241 242```php 243$promise = new Promise(); 244$promise->reject('foo'); 245$promise->wait(); 246``` 247 248> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo' 249 250 251## Unwrapping a promise 252 253When synchronously waiting on a promise, you are joining the state of the 254promise into the current state of execution (i.e., return the value of the 255promise if it was fulfilled or throw an exception if it was rejected). This is 256called "unwrapping" the promise. Waiting on a promise will by default unwrap 257the promise state. 258 259You can force a promise to resolve and *not* unwrap the state of the promise 260by passing `false` to the first argument of the `wait` function: 261 262```php 263$promise = new Promise(); 264$promise->reject('foo'); 265// This will not throw an exception. It simply ensures the promise has 266// been resolved. 267$promise->wait(false); 268``` 269 270When unwrapping a promise, the resolved value of the promise will be waited 271upon until the unwrapped value is not a promise. This means that if you resolve 272promise A with a promise B and unwrap promise A, the value returned by the 273wait function will be the value delivered to promise B. 274 275**Note**: when you do not unwrap the promise, no value is returned. 276 277 278# Cancellation 279 280You can cancel a promise that has not yet been fulfilled using the `cancel()` 281method of a promise. When creating a promise you can provide an optional 282cancel function that when invoked cancels the action of computing a resolution 283of the promise. 284 285 286# API 287 288 289## Promise 290 291When creating a promise object, you can provide an optional `$waitFn` and 292`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is 293expected to resolve the promise. `$cancelFn` is a function with no arguments 294that is expected to cancel the computation of a promise. It is invoked when the 295`cancel()` method of a promise is called. 296 297```php 298use GuzzleHttp\Promise\Promise; 299 300$promise = new Promise( 301 function () use (&$promise) { 302 $promise->resolve('waited'); 303 }, 304 function () { 305 // do something that will cancel the promise computation (e.g., close 306 // a socket, cancel a database query, etc...) 307 } 308); 309 310assert('waited' === $promise->wait()); 311``` 312 313A promise has the following methods: 314 315- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface` 316 317 Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler. 318 319- `otherwise(callable $onRejected) : PromiseInterface` 320 321 Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled. 322 323- `wait($unwrap = true) : mixed` 324 325 Synchronously waits on the promise to complete. 326 327 `$unwrap` controls whether or not the value of the promise is returned for a 328 fulfilled promise or if an exception is thrown if the promise is rejected. 329 This is set to `true` by default. 330 331- `cancel()` 332 333 Attempts to cancel the promise if possible. The promise being cancelled and 334 the parent most ancestor that has not yet been resolved will also be 335 cancelled. Any promises waiting on the cancelled promise to resolve will also 336 be cancelled. 337 338- `getState() : string` 339 340 Returns the state of the promise. One of `pending`, `fulfilled`, or 341 `rejected`. 342 343- `resolve($value)` 344 345 Fulfills the promise with the given `$value`. 346 347- `reject($reason)` 348 349 Rejects the promise with the given `$reason`. 350 351 352## FulfilledPromise 353 354A fulfilled promise can be created to represent a promise that has been 355fulfilled. 356 357```php 358use GuzzleHttp\Promise\FulfilledPromise; 359 360$promise = new FulfilledPromise('value'); 361 362// Fulfilled callbacks are immediately invoked. 363$promise->then(function ($value) { 364 echo $value; 365}); 366``` 367 368 369## RejectedPromise 370 371A rejected promise can be created to represent a promise that has been 372rejected. 373 374```php 375use GuzzleHttp\Promise\RejectedPromise; 376 377$promise = new RejectedPromise('Error'); 378 379// Rejected callbacks are immediately invoked. 380$promise->then(null, function ($reason) { 381 echo $reason; 382}); 383``` 384 385 386# Promise interop 387 388This library works with foreign promises that have a `then` method. This means 389you can use Guzzle promises with [React promises](https://github.com/reactphp/promise) 390for example. When a foreign promise is returned inside of a then method 391callback, promise resolution will occur recursively. 392 393```php 394// Create a React promise 395$deferred = new React\Promise\Deferred(); 396$reactPromise = $deferred->promise(); 397 398// Create a Guzzle promise that is fulfilled with a React promise. 399$guzzlePromise = new GuzzleHttp\Promise\Promise(); 400$guzzlePromise->then(function ($value) use ($reactPromise) { 401 // Do something something with the value... 402 // Return the React promise 403 return $reactPromise; 404}); 405``` 406 407Please note that wait and cancel chaining is no longer possible when forwarding 408a foreign promise. You will need to wrap a third-party promise with a Guzzle 409promise in order to utilize wait and cancel functions with foreign promises. 410 411 412## Event Loop Integration 413 414In order to keep the stack size constant, Guzzle promises are resolved 415asynchronously using a task queue. When waiting on promises synchronously, the 416task queue will be automatically run to ensure that the blocking promise and 417any forwarded promises are resolved. When using promises asynchronously in an 418event loop, you will need to run the task queue on each tick of the loop. If 419you do not run the task queue, then promises will not be resolved. 420 421You can run the task queue using the `run()` method of the global task queue 422instance. 423 424```php 425// Get the global task queue 426$queue = GuzzleHttp\Promise\Utils::queue(); 427$queue->run(); 428``` 429 430For example, you could use Guzzle promises with React using a periodic timer: 431 432```php 433$loop = React\EventLoop\Factory::create(); 434$loop->addPeriodicTimer(0, [$queue, 'run']); 435``` 436 437*TODO*: Perhaps adding a `futureTick()` on each tick would be faster? 438 439 440# Implementation notes 441 442 443## Promise resolution and chaining is handled iteratively 444 445By shuffling pending handlers from one owner to another, promises are 446resolved iteratively, allowing for "infinite" then chaining. 447 448```php 449<?php 450require 'vendor/autoload.php'; 451 452use GuzzleHttp\Promise\Promise; 453 454$parent = new Promise(); 455$p = $parent; 456 457for ($i = 0; $i < 1000; $i++) { 458 $p = $p->then(function ($v) { 459 // The stack size remains constant (a good thing) 460 echo xdebug_get_stack_depth() . ', '; 461 return $v + 1; 462 }); 463} 464 465$parent->resolve(0); 466var_dump($p->wait()); // int(1000) 467 468``` 469 470When a promise is fulfilled or rejected with a non-promise value, the promise 471then takes ownership of the handlers of each child promise and delivers values 472down the chain without using recursion. 473 474When a promise is resolved with another promise, the original promise transfers 475all of its pending handlers to the new promise. When the new promise is 476eventually resolved, all of the pending handlers are delivered the forwarded 477value. 478 479 480## A promise is the deferred. 481 482Some promise libraries implement promises using a deferred object to represent 483a computation and a promise object to represent the delivery of the result of 484the computation. This is a nice separation of computation and delivery because 485consumers of the promise cannot modify the value that will be eventually 486delivered. 487 488One side effect of being able to implement promise resolution and chaining 489iteratively is that you need to be able for one promise to reach into the state 490of another promise to shuffle around ownership of handlers. In order to achieve 491this without making the handlers of a promise publicly mutable, a promise is 492also the deferred value, allowing promises of the same parent class to reach 493into and modify the private properties of promises of the same type. While this 494does allow consumers of the value to modify the resolution or rejection of the 495deferred, it is a small price to pay for keeping the stack size constant. 496 497```php 498$promise = new Promise(); 499$promise->then(function ($value) { echo $value; }); 500// The promise is the deferred value, so you can deliver a value to it. 501$promise->resolve('foo'); 502// prints "foo" 503``` 504 505 506## Upgrading from Function API 507 508A static API was first introduced in 1.4.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API will be removed in 2.0.0. A migration table has been provided here for your convenience: 509 510| Original Function | Replacement Method | 511|----------------|----------------| 512| `queue` | `Utils::queue` | 513| `task` | `Utils::task` | 514| `promise_for` | `Create::promiseFor` | 515| `rejection_for` | `Create::rejectionFor` | 516| `exception_for` | `Create::exceptionFor` | 517| `iter_for` | `Create::iterFor` | 518| `inspect` | `Utils::inspect` | 519| `inspect_all` | `Utils::inspectAll` | 520| `unwrap` | `Utils::unwrap` | 521| `all` | `Utils::all` | 522| `some` | `Utils::some` | 523| `any` | `Utils::any` | 524| `settle` | `Utils::settle` | 525| `each` | `Each::of` | 526| `each_limit` | `Each::ofLimit` | 527| `each_limit_all` | `Each::ofLimitAll` | 528| `!is_fulfilled` | `Is::pending` | 529| `is_fulfilled` | `Is::fulfilled` | 530| `is_rejected` | `Is::rejected` | 531| `is_settled` | `Is::settled` | 532| `coroutine` | `Coroutine::of` | 533 534 535## Security 536 537If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/promises/security/policy) for more information. 538 539## License 540 541Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information. 542 543## For Enterprise 544 545Available as part of the Tidelift Subscription 546 547The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-promises?utm_source=packagist-guzzlehttp-promises&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) 548