• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..29-Apr-2022-

src/H29-Apr-2022-2,0391,037

CHANGELOG.mdH A D28-Apr-20221.8 KiB10454

LICENSEH A D28-Apr-20221.3 KiB2520

MakefileH A D28-Apr-2022189 149

README.mdH A D28-Apr-202217 KiB548399

composer.jsonH A D28-Apr-20221.5 KiB5958

README.md

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