Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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