friendship ended with social-app. php is my new best friend
1Promise 2======= 3 4A lightweight implementation of 5[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. 6 7[![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions) 8[![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise) 9 10Table of Contents 11----------------- 12 131. [Introduction](#introduction) 142. [Concepts](#concepts) 15 * [Deferred](#deferred) 16 * [Promise](#promise-1) 173. [API](#api) 18 * [Deferred](#deferred-1) 19 * [Deferred::promise()](#deferredpromise) 20 * [Deferred::resolve()](#deferredresolve) 21 * [Deferred::reject()](#deferredreject) 22 * [PromiseInterface](#promiseinterface) 23 * [PromiseInterface::then()](#promiseinterfacethen) 24 * [PromiseInterface::catch()](#promiseinterfacecatch) 25 * [PromiseInterface::finally()](#promiseinterfacefinally) 26 * [PromiseInterface::cancel()](#promiseinterfacecancel) 27 * [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise) 28 * [~~PromiseInterface::always()~~](#promiseinterfacealways) 29 * [Promise](#promise-2) 30 * [Functions](#functions) 31 * [resolve()](#resolve) 32 * [reject()](#reject) 33 * [all()](#all) 34 * [race()](#race) 35 * [any()](#any) 36 * [set_rejection_handler()](#set_rejection_handler) 374. [Examples](#examples) 38 * [How to use Deferred](#how-to-use-deferred) 39 * [How promise forwarding works](#how-promise-forwarding-works) 40 * [Resolution forwarding](#resolution-forwarding) 41 * [Rejection forwarding](#rejection-forwarding) 42 * [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding) 435. [Install](#install) 446. [Tests](#tests) 457. [Credits](#credits) 468. [License](#license) 47 48Introduction 49------------ 50 51Promise is a library implementing 52[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP. 53 54It also provides several other useful promise-related concepts, such as joining 55multiple promises and mapping and reducing collections of promises. 56 57If you've never heard about promises before, 58[read this first](https://gist.github.com/domenic/3889970). 59 60Concepts 61-------- 62 63### Deferred 64 65A **Deferred** represents a computation or unit of work that may not have 66completed yet. Typically (but not always), that computation will be something 67that executes asynchronously and completes at some point in the future. 68 69### Promise 70 71While a deferred represents the computation itself, a **Promise** represents 72the result of that computation. Thus, each deferred has a promise that acts as 73a placeholder for its actual result. 74 75API 76--- 77 78### Deferred 79 80A deferred represents an operation whose resolution is pending. It has separate 81promise and resolver parts. 82 83```php 84$deferred = new React\Promise\Deferred(); 85 86$promise = $deferred->promise(); 87 88$deferred->resolve(mixed $value); 89$deferred->reject(\Throwable $reason); 90``` 91 92The `promise` method returns the promise of the deferred. 93 94The `resolve` and `reject` methods control the state of the deferred. 95 96The constructor of the `Deferred` accepts an optional `$canceller` argument. 97See [Promise](#promise-2) for more information. 98 99#### Deferred::promise() 100 101```php 102$promise = $deferred->promise(); 103``` 104 105Returns the promise of the deferred, which you can hand out to others while 106keeping the authority to modify its state to yourself. 107 108#### Deferred::resolve() 109 110```php 111$deferred->resolve(mixed $value); 112``` 113 114Resolves the promise returned by `promise()`. All consumers are notified by 115having `$onFulfilled` (which they registered via `$promise->then()`) called with 116`$value`. 117 118If `$value` itself is a promise, the promise will transition to the state of 119this promise once it is resolved. 120 121See also the [`resolve()` function](#resolve). 122 123#### Deferred::reject() 124 125```php 126$deferred->reject(\Throwable $reason); 127``` 128 129Rejects the promise returned by `promise()`, signalling that the deferred's 130computation failed. 131All consumers are notified by having `$onRejected` (which they registered via 132`$promise->then()`) called with `$reason`. 133 134See also the [`reject()` function](#reject). 135 136### PromiseInterface 137 138The promise interface provides the common interface for all promise 139implementations. 140See [Promise](#promise-2) for the only public implementation exposed by this 141package. 142 143A promise represents an eventual outcome, which is either fulfillment (success) 144and an associated value, or rejection (failure) and an associated reason. 145 146Once in the fulfilled or rejected state, a promise becomes immutable. 147Neither its state nor its result (or error) can be modified. 148 149#### PromiseInterface::then() 150 151```php 152$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null); 153``` 154 155Transforms a promise's value by applying a function to the promise's fulfillment 156or rejection value. Returns a new promise for the transformed result. 157 158The `then()` method registers new fulfilled and rejection handlers with a promise 159(all parameters are optional): 160 161 * `$onFulfilled` will be invoked once the promise is fulfilled and passed 162 the result as the first argument. 163 * `$onRejected` will be invoked once the promise is rejected and passed the 164 reason as the first argument. 165 166It returns a new promise that will fulfill with the return value of either 167`$onFulfilled` or `$onRejected`, whichever is called, or will reject with 168the thrown exception if either throws. 169 170A promise makes the following guarantees about handlers registered in 171the same call to `then()`: 172 173 1. Only one of `$onFulfilled` or `$onRejected` will be called, 174 never both. 175 2. `$onFulfilled` and `$onRejected` will never be called more 176 than once. 177 178#### See also 179 180* [resolve()](#resolve) - Creating a resolved promise 181* [reject()](#reject) - Creating a rejected promise 182 183#### PromiseInterface::catch() 184 185```php 186$promise->catch(callable $onRejected); 187``` 188 189Registers a rejection handler for promise. It is a shortcut for: 190 191```php 192$promise->then(null, $onRejected); 193``` 194 195Additionally, you can type hint the `$reason` argument of `$onRejected` to catch 196only specific errors. 197 198```php 199$promise 200 ->catch(function (\RuntimeException $reason) { 201 // Only catch \RuntimeException instances 202 // All other types of errors will propagate automatically 203 }) 204 ->catch(function (\Throwable $reason) { 205 // Catch other errors 206 }); 207``` 208 209#### PromiseInterface::finally() 210 211```php 212$newPromise = $promise->finally(callable $onFulfilledOrRejected); 213``` 214 215Allows you to execute "cleanup" type tasks in a promise chain. 216 217It arranges for `$onFulfilledOrRejected` to be called, with no arguments, 218when the promise is either fulfilled or rejected. 219 220* If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully, 221 `$newPromise` will fulfill with the same value as `$promise`. 222* If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a 223 rejected promise, `$newPromise` will reject with the thrown exception or 224 rejected promise's reason. 225* If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully, 226 `$newPromise` will reject with the same reason as `$promise`. 227* If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a 228 rejected promise, `$newPromise` will reject with the thrown exception or 229 rejected promise's reason. 230 231`finally()` behaves similarly to the synchronous finally statement. When combined 232with `catch()`, `finally()` allows you to write code that is similar to the familiar 233synchronous catch/finally pair. 234 235Consider the following synchronous code: 236 237```php 238try { 239 return doSomething(); 240} catch (\Throwable $e) { 241 return handleError($e); 242} finally { 243 cleanup(); 244} 245``` 246 247Similar asynchronous code (with `doSomething()` that returns a promise) can be 248written: 249 250```php 251return doSomething() 252 ->catch('handleError') 253 ->finally('cleanup'); 254``` 255 256#### PromiseInterface::cancel() 257 258``` php 259$promise->cancel(); 260``` 261 262The `cancel()` method notifies the creator of the promise that there is no 263further interest in the results of the operation. 264 265Once a promise is settled (either fulfilled or rejected), calling `cancel()` on 266a promise has no effect. 267 268#### ~~PromiseInterface::otherwise()~~ 269 270> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead. 271 272The `otherwise()` method registers a rejection handler for a promise. 273 274This method continues to exist only for BC reasons and to ease upgrading 275between versions. It is an alias for: 276 277```php 278$promise->catch($onRejected); 279``` 280 281#### ~~PromiseInterface::always()~~ 282 283> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead. 284 285The `always()` method allows you to execute "cleanup" type tasks in a promise chain. 286 287This method continues to exist only for BC reasons and to ease upgrading 288between versions. It is an alias for: 289 290```php 291$promise->finally($onFulfilledOrRejected); 292``` 293 294### Promise 295 296Creates a promise whose state is controlled by the functions passed to 297`$resolver`. 298 299```php 300$resolver = function (callable $resolve, callable $reject) { 301 // Do some work, possibly asynchronously, and then 302 // resolve or reject. 303 304 $resolve($awesomeResult); 305 // or throw new Exception('Promise rejected'); 306 // or $resolve($anotherPromise); 307 // or $reject($nastyError); 308}; 309 310$canceller = function () { 311 // Cancel/abort any running operations like network connections, streams etc. 312 313 // Reject promise by throwing an exception 314 throw new Exception('Promise cancelled'); 315}; 316 317$promise = new React\Promise\Promise($resolver, $canceller); 318``` 319 320The promise constructor receives a resolver function and an optional canceller 321function which both will be called with two arguments: 322 323 * `$resolve($value)` - Primary function that seals the fate of the 324 returned promise. Accepts either a non-promise value, or another promise. 325 When called with a non-promise value, fulfills promise with that value. 326 When called with another promise, e.g. `$resolve($otherPromise)`, promise's 327 fate will be equivalent to that of `$otherPromise`. 328 * `$reject($reason)` - Function that rejects the promise. It is recommended to 329 just throw an exception instead of using `$reject()`. 330 331If the resolver or canceller throw an exception, the promise will be rejected 332with that thrown exception as the rejection reason. 333 334The resolver function will be called immediately, the canceller function only 335once all consumers called the `cancel()` method of the promise. 336 337### Functions 338 339Useful functions for creating and joining collections of promises. 340 341All functions working on promise collections (like `all()`, `race()`, 342etc.) support cancellation. This means, if you call `cancel()` on the returned 343promise, all promises in the collection are cancelled. 344 345#### resolve() 346 347```php 348$promise = React\Promise\resolve(mixed $promiseOrValue); 349``` 350 351Creates a promise for the supplied `$promiseOrValue`. 352 353If `$promiseOrValue` is a value, it will be the resolution value of the 354returned promise. 355 356If `$promiseOrValue` is a thenable (any object that provides a `then()` method), 357a trusted promise that follows the state of the thenable is returned. 358 359If `$promiseOrValue` is a promise, it will be returned as is. 360 361The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) 362and can be consumed like any other promise: 363 364```php 365$promise = React\Promise\resolve(42); 366 367$promise->then(function (int $result): void { 368 var_dump($result); 369}, function (\Throwable $e): void { 370 echo 'Error: ' . $e->getMessage() . PHP_EOL; 371}); 372``` 373 374#### reject() 375 376```php 377$promise = React\Promise\reject(\Throwable $reason); 378``` 379 380Creates a rejected promise for the supplied `$reason`. 381 382Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers 383both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and 384[`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to 385reject a promise, any language error or user land exception can be used to reject a promise. 386 387The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface) 388and can be consumed like any other promise: 389 390```php 391$promise = React\Promise\reject(new RuntimeException('Request failed')); 392 393$promise->then(function (int $result): void { 394 var_dump($result); 395}, function (\Throwable $e): void { 396 echo 'Error: ' . $e->getMessage() . PHP_EOL; 397}); 398``` 399 400Note that rejected promises should always be handled similar to how any 401exceptions should always be caught in a `try` + `catch` block. If you remove the 402last reference to a rejected promise that has not been handled, it will 403report an unhandled promise rejection: 404 405```php 406function incorrect(): int 407{ 408 $promise = React\Promise\reject(new RuntimeException('Request failed')); 409 410 // Commented out: No rejection handler registered here. 411 // $promise->then(null, function (\Throwable $e): void { /* ignore */ }); 412 413 // Returning from a function will remove all local variable references, hence why 414 // this will report an unhandled promise rejection here. 415 return 42; 416} 417 418// Calling this function will log an error message plus its stack trace: 419// Unhandled promise rejection with RuntimeException: Request failed in example.php:10 420incorrect(); 421``` 422 423A rejected promise will be considered "handled" if you catch the rejection 424reason with either the [`then()` method](#promiseinterfacethen), the 425[`catch()` method](#promiseinterfacecatch), or the 426[`finally()` method](#promiseinterfacefinally). Note that each of these methods 427return a new promise that may again be rejected if you re-throw an exception. 428 429A rejected promise will also be considered "handled" if you abort the operation 430with the [`cancel()` method](#promiseinterfacecancel) (which in turn would 431usually reject the promise if it is still pending). 432 433See also the [`set_rejection_handler()` function](#set_rejection_handler). 434 435#### all() 436 437```php 438$promise = React\Promise\all(iterable $promisesOrValues); 439``` 440 441Returns a promise that will resolve only once all the items in 442`$promisesOrValues` have resolved. The resolution value of the returned promise 443will be an array containing the resolution values of each of the items in 444`$promisesOrValues`. 445 446#### race() 447 448```php 449$promise = React\Promise\race(iterable $promisesOrValues); 450``` 451 452Initiates a competitive race that allows one winner. Returns a promise which is 453resolved in the same way the first settled promise resolves. 454 455The returned promise will become **infinitely pending** if `$promisesOrValues` 456contains 0 items. 457 458#### any() 459 460```php 461$promise = React\Promise\any(iterable $promisesOrValues); 462``` 463 464Returns a promise that will resolve when any one of the items in 465`$promisesOrValues` resolves. The resolution value of the returned promise 466will be the resolution value of the triggering item. 467 468The returned promise will only reject if *all* items in `$promisesOrValues` are 469rejected. The rejection value will be a `React\Promise\Exception\CompositeException` 470which holds all rejection reasons. The rejection reasons can be obtained with 471`CompositeException::getThrowables()`. 472 473The returned promise will also reject with a `React\Promise\Exception\LengthException` 474if `$promisesOrValues` contains 0 items. 475 476#### set_rejection_handler() 477 478```php 479React\Promise\set_rejection_handler(?callable $callback): ?callable; 480``` 481 482Sets the global rejection handler for unhandled promise rejections. 483 484Note that rejected promises should always be handled similar to how any 485exceptions should always be caught in a `try` + `catch` block. If you remove 486the last reference to a rejected promise that has not been handled, it will 487report an unhandled promise rejection. See also the [`reject()` function](#reject) 488for more details. 489 490The `?callable $callback` argument MUST be a valid callback function that 491accepts a single `Throwable` argument or a `null` value to restore the 492default promise rejection handler. The return value of the callback function 493will be ignored and has no effect, so you SHOULD return a `void` value. The 494callback function MUST NOT throw or the program will be terminated with a 495fatal error. 496 497The function returns the previous rejection handler or `null` if using the 498default promise rejection handler. 499 500The default promise rejection handler will log an error message plus its stack 501trace: 502 503```php 504// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2 505React\Promise\reject(new RuntimeException('Unhandled')); 506``` 507 508The promise rejection handler may be used to use customize the log message or 509write to custom log targets. As a rule of thumb, this function should only be 510used as a last resort and promise rejections are best handled with either the 511[`then()` method](#promiseinterfacethen), the 512[`catch()` method](#promiseinterfacecatch), or the 513[`finally()` method](#promiseinterfacefinally). 514See also the [`reject()` function](#reject) for more details. 515 516Examples 517-------- 518 519### How to use Deferred 520 521```php 522function getAwesomeResultPromise() 523{ 524 $deferred = new React\Promise\Deferred(); 525 526 // Execute a Node.js-style function using the callback pattern 527 computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) { 528 if ($error) { 529 $deferred->reject($error); 530 } else { 531 $deferred->resolve($result); 532 } 533 }); 534 535 // Return the promise 536 return $deferred->promise(); 537} 538 539getAwesomeResultPromise() 540 ->then( 541 function ($value) { 542 // Deferred resolved, do something with $value 543 }, 544 function (\Throwable $reason) { 545 // Deferred rejected, do something with $reason 546 } 547 ); 548``` 549 550### How promise forwarding works 551 552A few simple examples to show how the mechanics of Promises/A forwarding works. 553These examples are contrived, of course, and in real usage, promise chains will 554typically be spread across several function calls, or even several levels of 555your application architecture. 556 557#### Resolution forwarding 558 559Resolved promises forward resolution values to the next promise. 560The first promise, `$deferred->promise()`, will resolve with the value passed 561to `$deferred->resolve()` below. 562 563Each call to `then()` returns a new promise that will resolve with the return 564value of the previous handler. This creates a promise "pipeline". 565 566```php 567$deferred = new React\Promise\Deferred(); 568 569$deferred->promise() 570 ->then(function ($x) { 571 // $x will be the value passed to $deferred->resolve() below 572 // and returns a *new promise* for $x + 1 573 return $x + 1; 574 }) 575 ->then(function ($x) { 576 // $x === 2 577 // This handler receives the return value of the 578 // previous handler. 579 return $x + 1; 580 }) 581 ->then(function ($x) { 582 // $x === 3 583 // This handler receives the return value of the 584 // previous handler. 585 return $x + 1; 586 }) 587 ->then(function ($x) { 588 // $x === 4 589 // This handler receives the return value of the 590 // previous handler. 591 echo 'Resolve ' . $x; 592 }); 593 594$deferred->resolve(1); // Prints "Resolve 4" 595``` 596 597#### Rejection forwarding 598 599Rejected promises behave similarly, and also work similarly to try/catch: 600When you catch an exception, you must rethrow for it to propagate. 601 602Similarly, when you handle a rejected promise, to propagate the rejection, 603"rethrow" it by either returning a rejected promise, or actually throwing 604(since promise translates thrown exceptions into rejections) 605 606```php 607$deferred = new React\Promise\Deferred(); 608 609$deferred->promise() 610 ->then(function ($x) { 611 throw new \Exception($x + 1); 612 }) 613 ->catch(function (\Exception $x) { 614 // Propagate the rejection 615 throw $x; 616 }) 617 ->catch(function (\Exception $x) { 618 // Can also propagate by returning another rejection 619 return React\Promise\reject( 620 new \Exception($x->getMessage() + 1) 621 ); 622 }) 623 ->catch(function ($x) { 624 echo 'Reject ' . $x->getMessage(); // 3 625 }); 626 627$deferred->resolve(1); // Prints "Reject 3" 628``` 629 630#### Mixed resolution and rejection forwarding 631 632Just like try/catch, you can choose to propagate or not. Mixing resolutions and 633rejections will still forward handler results in a predictable way. 634 635```php 636$deferred = new React\Promise\Deferred(); 637 638$deferred->promise() 639 ->then(function ($x) { 640 return $x + 1; 641 }) 642 ->then(function ($x) { 643 throw new \Exception($x + 1); 644 }) 645 ->catch(function (\Exception $x) { 646 // Handle the rejection, and don't propagate. 647 // This is like catch without a rethrow 648 return $x->getMessage() + 1; 649 }) 650 ->then(function ($x) { 651 echo 'Mixed ' . $x; // 4 652 }); 653 654$deferred->resolve(1); // Prints "Mixed 4" 655``` 656 657Install 658------- 659 660The recommended way to install this library is [through Composer](https://getcomposer.org/). 661[New to Composer?](https://getcomposer.org/doc/00-intro.md) 662 663This project follows [SemVer](https://semver.org/). 664This will install the latest supported version from this branch: 665 666```bash 667composer require react/promise:^3.2 668``` 669 670See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. 671 672This project aims to run on any platform and thus does not require any PHP 673extensions and supports running on PHP 7.1 through current PHP 8+. 674It's *highly recommended to use the latest supported PHP version* for this project. 675 676We're committed to providing long-term support (LTS) options and to provide a 677smooth upgrade path. If you're using an older PHP version, you may use the 678[`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or 679[`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both 680provide a compatible API but do not take advantage of newer language features. 681You may target multiple versions at the same time to support a wider range of 682PHP versions like this: 683 684```bash 685composer require "react/promise:^3 || ^2 || ^1" 686``` 687 688## Tests 689 690To run the test suite, you first need to clone this repo and then install all 691dependencies [through Composer](https://getcomposer.org/): 692 693```bash 694composer install 695``` 696 697To run the test suite, go to the project root and run: 698 699```bash 700vendor/bin/phpunit 701``` 702 703On top of this, we use PHPStan on max level to ensure type safety across the project: 704 705```bash 706vendor/bin/phpstan 707``` 708 709Credits 710------- 711 712Promise is a port of [when.js](https://github.com/cujojs/when) 713by [Brian Cavalier](https://github.com/briancavalier). 714 715Also, large parts of the documentation have been ported from the when.js 716[Wiki](https://github.com/cujojs/when/wiki) and the 717[API docs](https://github.com/cujojs/when/blob/master/docs/api.md). 718 719License 720------- 721 722Released under the [MIT](LICENSE) license.