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[](https://github.com/reactphp/promise/actions)
8[](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.