krz/vox-populi
A Tumblr web client.
clone: git clone https://gitbay.org/krz/vox-populi.git
main: vendor/guzzlehttp/promises/README.md · raw
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()`.
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;
185use GuzzleHttp\Promise\RejectedPromise;
186
187$promise = new Promise();
188$promise
189 ->then(null, function ($reason) {
190 return "It's ok";
191 })
192 ->then(function ($value) {
193 assert($value === "It's ok");
194 });
195
196$promise->reject('Error!');
197```
198
199# Synchronous wait
200
201You can synchronously force promises to complete using a promise's `wait`
202method. When creating a promise, you can provide a wait function that is used
203to synchronously force a promise to complete. When a wait function is invoked
204it is expected to deliver a value to the promise or reject the promise. If the
205wait function does not deliver a value, then an exception is thrown. The wait
206function provided to a promise constructor is invoked when the `wait` function
207of the promise is called.
208
209```php
210$promise = new Promise(function () use (&$promise) {
211 $promise->resolve('foo');
212});
213
214// Calling wait will return the value of the promise.
215echo $promise->wait(); // outputs "foo"
216```
217
218If an exception is encountered while invoking the wait function of a promise,
219the promise is rejected with the exception and the exception is thrown.
220
221```php
222$promise = new Promise(function () use (&$promise) {
223 throw new \Exception('foo');
224});
225
226$promise->wait(); // throws the exception.
227```
228
229Calling `wait` on a promise that has been fulfilled will not trigger the wait
230function. It will simply return the previously resolved value.
231
232```php
233$promise = new Promise(function () { die('this is not called!'); });
234$promise->resolve('foo');
235echo $promise->wait(); // outputs "foo"
236```
237
238Calling `wait` on a promise that has been rejected will throw an exception. If
239the rejection reason is an instance of `\Exception` the reason is thrown.
240Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
241can be obtained by calling the `getReason` method of the exception.
242
243```php
244$promise = new Promise();
245$promise->reject('foo');
246$promise->wait();
247```
248
249> PHP Fatal error: Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'
250
251
252## Unwrapping a promise
253
254When synchronously waiting on a promise, you are joining the state of the
255promise into the current state of execution (i.e., return the value of the
256promise if it was fulfilled or throw an exception if it was rejected). This is
257called "unwrapping" the promise. Waiting on a promise will by default unwrap
258the promise state.
259
260You can force a promise to resolve and *not* unwrap the state of the promise
261by passing `false` to the first argument of the `wait` function:
262
263```php
264$promise = new Promise();
265$promise->reject('foo');
266// This will not throw an exception. It simply ensures the promise has
267// been resolved.
268$promise->wait(false);
269```
270
271When unwrapping a promise, the resolved value of the promise will be waited
272upon until the unwrapped value is not a promise. This means that if you resolve
273promise A with a promise B and unwrap promise A, the value returned by the
274wait function will be the value delivered to promise B.
275
276**Note**: when you do not unwrap the promise, no value is returned.
277
278
279# Cancellation
280
281You can cancel a promise that has not yet been fulfilled using the `cancel()`
282method of a promise. When creating a promise you can provide an optional
283cancel function that when invoked cancels the action of computing a resolution
284of the promise.
285
286
287# API
288
289
290## Promise
291
292When creating a promise object, you can provide an optional `$waitFn` and
293`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
294expected to resolve the promise. `$cancelFn` is a function with no arguments
295that is expected to cancel the computation of a promise. It is invoked when the
296`cancel()` method of a promise is called.
297
298```php
299use GuzzleHttp\Promise\Promise;
300
301$promise = new Promise(
302 function () use (&$promise) {
303 $promise->resolve('waited');
304 },
305 function () {
306 // do something that will cancel the promise computation (e.g., close
307 // a socket, cancel a database query, etc...)
308 }
309);
310
311assert('waited' === $promise->wait());
312```
313
314A promise has the following methods:
315
316- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
317
318 Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.
319
320- `otherwise(callable $onRejected) : PromiseInterface`
321
322 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.
323
324- `wait($unwrap = true) : mixed`
325
326 Synchronously waits on the promise to complete.
327
328 `$unwrap` controls whether or not the value of the promise is returned for a
329 fulfilled promise or if an exception is thrown if the promise is rejected.
330 This is set to `true` by default.
331
332- `cancel()`
333
334 Attempts to cancel the promise if possible. The promise being cancelled and
335 the parent most ancestor that has not yet been resolved will also be
336 cancelled. Any promises waiting on the cancelled promise to resolve will also
337 be cancelled.
338
339- `getState() : string`
340
341 Returns the state of the promise. One of `pending`, `fulfilled`, or
342 `rejected`.
343
344- `resolve($value)`
345
346 Fulfills the promise with the given `$value`.
347
348- `reject($reason)`
349
350 Rejects the promise with the given `$reason`.
351
352
353## FulfilledPromise
354
355A fulfilled promise can be created to represent a promise that has been
356fulfilled.
357
358```php
359use GuzzleHttp\Promise\FulfilledPromise;
360
361$promise = new FulfilledPromise('value');
362
363// Fulfilled callbacks are immediately invoked.
364$promise->then(function ($value) {
365 echo $value;
366});
367```
368
369
370## RejectedPromise
371
372A rejected promise can be created to represent a promise that has been
373rejected.
374
375```php
376use GuzzleHttp\Promise\RejectedPromise;
377
378$promise = new RejectedPromise('Error');
379
380// Rejected callbacks are immediately invoked.
381$promise->then(null, function ($reason) {
382 echo $reason;
383});
384```
385
386
387# Promise interop
388
389This library works with foreign promises that have a `then` method. This means
390you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
391for example. When a foreign promise is returned inside of a then method
392callback, promise resolution will occur recursively.
393
394```php
395// Create a React promise
396$deferred = new React\Promise\Deferred();
397$reactPromise = $deferred->promise();
398
399// Create a Guzzle promise that is fulfilled with a React promise.
400$guzzlePromise = new \GuzzleHttp\Promise\Promise();
401$guzzlePromise->then(function ($value) use ($reactPromise) {
402 // Do something something with the value...
403 // Return the React promise
404 return $reactPromise;
405});
406```
407
408Please note that wait and cancel chaining is no longer possible when forwarding
409a foreign promise. You will need to wrap a third-party promise with a Guzzle
410promise in order to utilize wait and cancel functions with foreign promises.
411
412
413## Event Loop Integration
414
415In order to keep the stack size constant, Guzzle promises are resolved
416asynchronously using a task queue. When waiting on promises synchronously, the
417task queue will be automatically run to ensure that the blocking promise and
418any forwarded promises are resolved. When using promises asynchronously in an
419event loop, you will need to run the task queue on each tick of the loop. If
420you do not run the task queue, then promises will not be resolved.
421
422You can run the task queue using the `run()` method of the global task queue
423instance.
424
425```php
426// Get the global task queue
427$queue = \GuzzleHttp\Promise\queue();
428$queue->run();
429```
430
431For example, you could use Guzzle promises with React using a periodic timer:
432
433```php
434$loop = React\EventLoop\Factory::create();
435$loop->addPeriodicTimer(0, [$queue, 'run']);
436```
437
438*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
439
440
441# Implementation notes
442
443
444## Promise resolution and chaining is handled iteratively
445
446By shuffling pending handlers from one owner to another, promises are
447resolved iteratively, allowing for "infinite" then chaining.
448
449```php
450<?php
451require 'vendor/autoload.php';
452
453use GuzzleHttp\Promise\Promise;
454
455$parent = new Promise();
456$p = $parent;
457
458for ($i = 0; $i < 1000; $i++) {
459 $p = $p->then(function ($v) {
460 // The stack size remains constant (a good thing)
461 echo xdebug_get_stack_depth() . ', ';
462 return $v + 1;
463 });
464}
465
466$parent->resolve(0);
467var_dump($p->wait()); // int(1000)
468
469```
470
471When a promise is fulfilled or rejected with a non-promise value, the promise
472then takes ownership of the handlers of each child promise and delivers values
473down the chain without using recursion.
474
475When a promise is resolved with another promise, the original promise transfers
476all of its pending handlers to the new promise. When the new promise is
477eventually resolved, all of the pending handlers are delivered the forwarded
478value.
479
480
481## A promise is the deferred.
482
483Some promise libraries implement promises using a deferred object to represent
484a computation and a promise object to represent the delivery of the result of
485the computation. This is a nice separation of computation and delivery because
486consumers of the promise cannot modify the value that will be eventually
487delivered.
488
489One side effect of being able to implement promise resolution and chaining
490iteratively is that you need to be able for one promise to reach into the state
491of another promise to shuffle around ownership of handlers. In order to achieve
492this without making the handlers of a promise publicly mutable, a promise is
493also the deferred value, allowing promises of the same parent class to reach
494into and modify the private properties of promises of the same type. While this
495does allow consumers of the value to modify the resolution or rejection of the
496deferred, it is a small price to pay for keeping the stack size constant.
497
498```php
499$promise = new Promise();
500$promise->then(function ($value) { echo $value; });
501// The promise is the deferred value, so you can deliver a value to it.
502$promise->resolve('foo');
503// prints "foo"
504```