最新服务器上的版本,以后用这个
wangzhenxin
2023-11-19 bc164b8bdbfbdf1d8229a5ced6b08d7cb8db7361
commit | author | age
2207d6 1 # Guzzle Promises
W 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()`.
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
40 ## Callbacks
41
42 Callbacks are registered with the `then` method by providing an optional 
43 `$onFulfilled` followed by an optional `$onRejected` function.
44
45
46 ```php
47 use 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
63 reject a promise with a *reason*. Resolving a promises triggers callbacks
64 registered with the promises's `then` method. These callbacks are triggered
65 only once and in the order in which they were added.
66
67
68 ## Resolving a promise
69
70 Promises are fulfilled using the `resolve($value)` method. Resolving a promise
71 with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
72 all of the onFulfilled callbacks (resolving a promise with a rejected promise
73 will reject the promise and trigger the `$onRejected` callbacks).
74
75 ```php
76 use 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
98 Promises can be chained one after the other. Each then in the chain is a new
99 promise. The return value of a promise is what's forwarded to the next
100 promise in the chain. Returning a promise in a `then` callback will cause the
101 subsequent promises in the chain to only be fulfilled when the returned promise
102 has been fulfilled. The next promise in the chain will be invoked with the
103 resolved value of the promise.
104
105 ```php
106 use 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
128 When a promise is rejected, the `$onRejected` callbacks are invoked with the
129 rejection reason.
130
131 ```php
132 use 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
145 If an exception is thrown in an `$onRejected` callback, subsequent
146 `$onRejected` callbacks are invoked with the thrown exception as the reason.
147
148 ```php
149 use 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
161 You 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
166 use GuzzleHttp\Promise\Promise;
167 use 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
179 If an exception is not thrown in a `$onRejected` callback and the callback
180 does not return a rejected promise, downstream `$onFulfilled` callbacks are
181 invoked using the value returned from the `$onRejected` callback.
182
183 ```php
184 use GuzzleHttp\Promise\Promise;
185 use 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
201 You can synchronously force promises to complete using a promise's `wait`
202 method. When creating a promise, you can provide a wait function that is used
203 to synchronously force a promise to complete. When a wait function is invoked
204 it is expected to deliver a value to the promise or reject the promise. If the
205 wait function does not deliver a value, then an exception is thrown. The wait
206 function provided to a promise constructor is invoked when the `wait` function
207 of 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.
215 echo $promise->wait(); // outputs "foo"
216 ```
217
218 If an exception is encountered while invoking the wait function of a promise,
219 the 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
229 Calling `wait` on a promise that has been fulfilled will not trigger the wait
230 function. 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');
235 echo $promise->wait(); // outputs "foo"
236 ```
237
238 Calling `wait` on a promise that has been rejected will throw an exception. If
239 the rejection reason is an instance of `\Exception` the reason is thrown.
240 Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
241 can 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
254 When synchronously waiting on a promise, you are joining the state of the
255 promise into the current state of execution (i.e., return the value of the
256 promise if it was fulfilled or throw an exception if it was rejected). This is
257 called "unwrapping" the promise. Waiting on a promise will by default unwrap
258 the promise state.
259
260 You can force a promise to resolve and *not* unwrap the state of the promise
261 by 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
271 When unwrapping a promise, the resolved value of the promise will be waited
272 upon until the unwrapped value is not a promise. This means that if you resolve
273 promise A with a promise B and unwrap promise A, the value returned by the
274 wait 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
281 You can cancel a promise that has not yet been fulfilled using the `cancel()`
282 method of a promise. When creating a promise you can provide an optional
283 cancel function that when invoked cancels the action of computing a resolution
284 of the promise.
285
286
287 # API
288
289
290 ## Promise
291
292 When 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
294 expected to resolve the promise. `$cancelFn` is a function with no arguments
295 that 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
299 use 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
311 assert('waited' === $promise->wait());
312 ```
313
314 A 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
355 A fulfilled promise can be created to represent a promise that has been
356 fulfilled.
357
358 ```php
359 use 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
372 A rejected promise can be created to represent a promise that has been
373 rejected.
374
375 ```php
376 use 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
389 This library works with foreign promises that have a `then` method. This means
390 you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
391 for example. When a foreign promise is returned inside of a then method
392 callback, 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
408 Please note that wait and cancel chaining is no longer possible when forwarding
409 a foreign promise. You will need to wrap a third-party promise with a Guzzle
410 promise in order to utilize wait and cancel functions with foreign promises.
411
412
413 ## Event Loop Integration
414
415 In order to keep the stack size constant, Guzzle promises are resolved
416 asynchronously using a task queue. When waiting on promises synchronously, the
417 task queue will be automatically run to ensure that the blocking promise and
418 any forwarded promises are resolved. When using promises asynchronously in an
419 event loop, you will need to run the task queue on each tick of the loop. If
420 you do not run the task queue, then promises will not be resolved.
421
422 You can run the task queue using the `run()` method of the global task queue
423 instance.
424
425 ```php
426 // Get the global task queue
427 $queue = \GuzzleHttp\Promise\queue();
428 $queue->run();
429 ```
430
431 For 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
446 By shuffling pending handlers from one owner to another, promises are
447 resolved iteratively, allowing for "infinite" then chaining.
448
449 ```php
450 <?php
451 require 'vendor/autoload.php';
452
453 use GuzzleHttp\Promise\Promise;
454
455 $parent = new Promise();
456 $p = $parent;
457
458 for ($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);
467 var_dump($p->wait()); // int(1000)
468
469 ```
470
471 When a promise is fulfilled or rejected with a non-promise value, the promise
472 then takes ownership of the handlers of each child promise and delivers values
473 down the chain without using recursion.
474
475 When a promise is resolved with another promise, the original promise transfers
476 all of its pending handlers to the new promise. When the new promise is
477 eventually resolved, all of the pending handlers are delivered the forwarded
478 value.
479
480
481 ## A promise is the deferred.
482
483 Some promise libraries implement promises using a deferred object to represent
484 a computation and a promise object to represent the delivery of the result of
485 the computation. This is a nice separation of computation and delivery because
486 consumers of the promise cannot modify the value that will be eventually
487 delivered.
488
489 One side effect of being able to implement promise resolution and chaining
490 iteratively is that you need to be able for one promise to reach into the state
491 of another promise to shuffle around ownership of handlers. In order to achieve
492 this without making the handlers of a promise publicly mutable, a promise is
493 also the deferred value, allowing promises of the same parent class to reach
494 into and modify the private properties of promises of the same type. While this
495 does allow consumers of the value to modify the resolution or rejection of the
496 deferred, 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 ```