The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
└── src
    ├── AggregateException.php
    ├── CancellationException.php
    ├── Coroutine.php
    ├── Create.php
    ├── Each.php
    ├── EachPromise.php
    ├── FulfilledPromise.php
    ├── Is.php
    ├── Promise.php
    ├── PromiseInterface.php
    ├── PromisorInterface.php
    ├── RejectedPromise.php
    ├── RejectionException.php
    ├── TaskQueue.php
    ├── TaskQueueInterface.php
    └── Utils.php


/CHANGELOG.md:
--------------------------------------------------------------------------------
  1 | # CHANGELOG
  2 | 
  3 | 
  4 | ## 2.2.0 - 2025-03-27
  5 | 
  6 | ### Fixed
  7 | 
  8 | - Revert "Allow an empty EachPromise to be resolved by running the queue"
  9 | 
 10 | 
 11 | ## 2.1.0 - 2025-03-27
 12 | 
 13 | ### Added
 14 | 
 15 | - Allow an empty EachPromise to be resolved by running the queue
 16 | 
 17 | 
 18 | ## 2.0.4 - 2024-10-17
 19 | 
 20 | ### Fixed
 21 | 
 22 | - Once settled, don't allow further rejection of additional promises
 23 | 
 24 | 
 25 | ## 2.0.3 - 2024-07-18
 26 | 
 27 | ### Changed
 28 | 
 29 | - PHP 8.4 support
 30 | 
 31 | 
 32 | ## 2.0.2 - 2023-12-03
 33 | 
 34 | ### Changed
 35 | 
 36 | - Replaced `call_user_func*` with native calls
 37 | 
 38 | 
 39 | ## 2.0.1 - 2023-08-03
 40 | 
 41 | ### Changed
 42 | 
 43 | - PHP 8.3 support
 44 | 
 45 | 
 46 | ## 2.0.0 - 2023-05-21
 47 | 
 48 | ### Added
 49 | 
 50 | - Added PHP 7 type hints
 51 | 
 52 | ### Changed
 53 | 
 54 | - All previously non-final non-exception classes have been marked as soft-final
 55 | 
 56 | ### Removed
 57 | 
 58 | - Dropped PHP < 7.2 support
 59 | - All functions in the `GuzzleHttp\Promise` namespace
 60 | 
 61 | 
 62 | ## 1.5.3 - 2023-05-21
 63 | 
 64 | ### Changed
 65 | 
 66 | - Removed remaining usage of deprecated functions
 67 | 
 68 | 
 69 | ## 1.5.2 - 2022-08-07
 70 | 
 71 | ### Changed
 72 | 
 73 | - Officially support PHP 8.2
 74 | 
 75 | 
 76 | ## 1.5.1 - 2021-10-22
 77 | 
 78 | ### Fixed
 79 | 
 80 | - Revert "Call handler when waiting on fulfilled/rejected Promise"
 81 | - Fix pool memory leak when empty array of promises provided
 82 | 
 83 | 
 84 | ## 1.5.0 - 2021-10-07
 85 | 
 86 | ### Changed
 87 | 
 88 | - Call handler when waiting on fulfilled/rejected Promise
 89 | - Officially support PHP 8.1
 90 | 
 91 | ### Fixed
 92 | 
 93 | - Fix manually settle promises generated with `Utils::task`
 94 | 
 95 | 
 96 | ## 1.4.1 - 2021-02-18
 97 | 
 98 | ### Fixed
 99 | 
100 | - Fixed `each_limit` skipping promises and failing
101 | 
102 | 
103 | ## 1.4.0 - 2020-09-30
104 | 
105 | ### Added
106 | 
107 | - Support for PHP 8
108 | - Optional `$recursive` flag to `all`
109 | - Replaced functions by static methods
110 | 
111 | ### Fixed
112 | 
113 | - Fix empty `each` processing
114 | - Fix promise handling for Iterators of non-unique keys
115 | - Fixed `method_exists` crashes on PHP 8
116 | - Memory leak on exceptions
117 | 
118 | 
119 | ## 1.3.1 - 2016-12-20
120 | 
121 | ### Fixed
122 | 
123 | - `wait()` foreign promise compatibility
124 | 
125 | 
126 | ## 1.3.0 - 2016-11-18
127 | 
128 | ### Added
129 | 
130 | - Adds support for custom task queues.
131 | 
132 | ### Fixed
133 | 
134 | - Fixed coroutine promise memory leak.
135 | 
136 | 
137 | ## 1.2.0 - 2016-05-18
138 | 
139 | ### Changed
140 | 
141 | - Update to now catch `\Throwable` on PHP 7+
142 | 
143 | 
144 | ## 1.1.0 - 2016-03-07
145 | 
146 | ### Changed
147 | 
148 | - Update EachPromise to prevent recurring on a iterator when advancing, as this
149 |   could trigger fatal generator errors.
150 | - Update Promise to allow recursive waiting without unwrapping exceptions.
151 | 
152 | 
153 | ## 1.0.3 - 2015-10-15
154 | 
155 | ### Changed
156 | 
157 | - Update EachPromise to immediately resolve when the underlying promise iterator
158 |   is empty. Previously, such a promise would throw an exception when its `wait`
159 |   function was called.
160 | 
161 | 
162 | ## 1.0.2 - 2015-05-15
163 | 
164 | ### Changed
165 | 
166 | - Conditionally require functions.php.
167 | 
168 | 
169 | ## 1.0.1 - 2015-06-24
170 | 
171 | ### Changed
172 | 
173 | - Updating EachPromise to call next on the underlying promise iterator as late
174 |   as possible to ensure that generators that generate new requests based on
175 |   callbacks are not iterated until after callbacks are invoked.
176 | 
177 | 
178 | ## 1.0.0 - 2015-05-12
179 | 
180 | - Initial release
181 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com>
 4 | Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk>
 5 | Copyright (c) 2017 Tobias Schultze <webmaster@tubo-world.de>
 6 | Copyright (c) 2020 Tobias Nyholm <tobias.nyholm@gmail.com>
 7 | 
 8 | Permission is hereby granted, free of charge, to any person obtaining a copy
 9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 | 
15 | The above copyright notice and this permission notice shall be included in
16 | all copies or substantial portions of the Software.
17 | 
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | THE SOFTWARE.
25 | 


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


--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "name": "guzzlehttp/promises",
 3 |     "description": "Guzzle promises library",
 4 |     "keywords": ["promise"],
 5 |     "license": "MIT",
 6 |     "authors": [
 7 |         {
 8 |             "name": "Graham Campbell",
 9 |             "email": "hello@gjcampbell.co.uk",
10 |             "homepage": "https://github.com/GrahamCampbell"
11 |         },
12 |         {
13 |             "name": "Michael Dowling",
14 |             "email": "mtdowling@gmail.com",
15 |             "homepage": "https://github.com/mtdowling"
16 |         },
17 |         {
18 |             "name": "Tobias Nyholm",
19 |             "email": "tobias.nyholm@gmail.com",
20 |             "homepage": "https://github.com/Nyholm"
21 |         },
22 |         {
23 |             "name": "Tobias Schultze",
24 |             "email": "webmaster@tubo-world.de",
25 |             "homepage": "https://github.com/Tobion"
26 |         }
27 |     ],
28 |     "require": {
29 |         "php": "^7.2.5 || ^8.0"
30 |     },
31 |     "require-dev": {
32 |         "bamarni/composer-bin-plugin": "^1.8.2",
33 |         "phpunit/phpunit": "^8.5.39 || ^9.6.20"
34 |     },
35 |     "autoload": {
36 |         "psr-4": {
37 |             "GuzzleHttp\\Promise\\": "src/"
38 |         }
39 |     },
40 |     "autoload-dev": {
41 |         "psr-4": {
42 |             "GuzzleHttp\\Promise\\Tests\\": "tests/"
43 |         }
44 |     },
45 |     "extra": {
46 |         "bamarni-bin": {
47 |             "bin-links": true,
48 |             "forward-command": false
49 |         }
50 |     },
51 |     "config": {
52 |         "allow-plugins": {
53 |             "bamarni/composer-bin-plugin": true
54 |         },
55 |         "preferred-install": "dist",
56 |         "sort-packages": true
57 |     }
58 | }
59 | 


--------------------------------------------------------------------------------
/src/AggregateException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * Exception thrown when too many errors occur in the some() or any() methods.
 9 |  */
10 | class AggregateException extends RejectionException
11 | {
12 |     public function __construct(string $msg, array $reasons)
13 |     {
14 |         parent::__construct(
15 |             $reasons,
16 |             sprintf('%s; %d rejected promises', $msg, count($reasons))
17 |         );
18 |     }
19 | }
20 | 


--------------------------------------------------------------------------------
/src/CancellationException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * Exception that is set as the reason for a promise that has been cancelled.
 9 |  */
10 | class CancellationException extends RejectionException
11 | {
12 | }
13 | 


--------------------------------------------------------------------------------
/src/Coroutine.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | declare(strict_types=1);
  4 | 
  5 | namespace GuzzleHttp\Promise;
  6 | 
  7 | use Generator;
  8 | use Throwable;
  9 | 
 10 | /**
 11 |  * Creates a promise that is resolved using a generator that yields values or
 12 |  * promises (somewhat similar to C#'s async keyword).
 13 |  *
 14 |  * When called, the Coroutine::of method will start an instance of the generator
 15 |  * and returns a promise that is fulfilled with its final yielded value.
 16 |  *
 17 |  * Control is returned back to the generator when the yielded promise settles.
 18 |  * This can lead to less verbose code when doing lots of sequential async calls
 19 |  * with minimal processing in between.
 20 |  *
 21 |  *     use GuzzleHttp\Promise;
 22 |  *
 23 |  *     function createPromise($value) {
 24 |  *         return new Promise\FulfilledPromise($value);
 25 |  *     }
 26 |  *
 27 |  *     $promise = Promise\Coroutine::of(function () {
 28 |  *         $value = (yield createPromise('a'));
 29 |  *         try {
 30 |  *             $value = (yield createPromise($value . 'b'));
 31 |  *         } catch (\Throwable $e) {
 32 |  *             // The promise was rejected.
 33 |  *         }
 34 |  *         yield $value . 'c';
 35 |  *     });
 36 |  *
 37 |  *     // Outputs "abc"
 38 |  *     $promise->then(function ($v) { echo $v; });
 39 |  *
 40 |  * @param callable $generatorFn Generator function to wrap into a promise.
 41 |  *
 42 |  * @return Promise
 43 |  *
 44 |  * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
 45 |  */
 46 | final class Coroutine implements PromiseInterface
 47 | {
 48 |     /**
 49 |      * @var PromiseInterface|null
 50 |      */
 51 |     private $currentPromise;
 52 | 
 53 |     /**
 54 |      * @var Generator
 55 |      */
 56 |     private $generator;
 57 | 
 58 |     /**
 59 |      * @var Promise
 60 |      */
 61 |     private $result;
 62 | 
 63 |     public function __construct(callable $generatorFn)
 64 |     {
 65 |         $this->generator = $generatorFn();
 66 |         $this->result = new Promise(function (): void {
 67 |             while (isset($this->currentPromise)) {
 68 |                 $this->currentPromise->wait();
 69 |             }
 70 |         });
 71 |         try {
 72 |             $this->nextCoroutine($this->generator->current());
 73 |         } catch (Throwable $throwable) {
 74 |             $this->result->reject($throwable);
 75 |         }
 76 |     }
 77 | 
 78 |     /**
 79 |      * Create a new coroutine.
 80 |      */
 81 |     public static function of(callable $generatorFn): self
 82 |     {
 83 |         return new self($generatorFn);
 84 |     }
 85 | 
 86 |     public function then(
 87 |         ?callable $onFulfilled = null,
 88 |         ?callable $onRejected = null
 89 |     ): PromiseInterface {
 90 |         return $this->result->then($onFulfilled, $onRejected);
 91 |     }
 92 | 
 93 |     public function otherwise(callable $onRejected): PromiseInterface
 94 |     {
 95 |         return $this->result->otherwise($onRejected);
 96 |     }
 97 | 
 98 |     public function wait(bool $unwrap = true)
 99 |     {
100 |         return $this->result->wait($unwrap);
101 |     }
102 | 
103 |     public function getState(): string
104 |     {
105 |         return $this->result->getState();
106 |     }
107 | 
108 |     public function resolve($value): void
109 |     {
110 |         $this->result->resolve($value);
111 |     }
112 | 
113 |     public function reject($reason): void
114 |     {
115 |         $this->result->reject($reason);
116 |     }
117 | 
118 |     public function cancel(): void
119 |     {
120 |         $this->currentPromise->cancel();
121 |         $this->result->cancel();
122 |     }
123 | 
124 |     private function nextCoroutine($yielded): void
125 |     {
126 |         $this->currentPromise = Create::promiseFor($yielded)
127 |             ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
128 |     }
129 | 
130 |     /**
131 |      * @internal
132 |      */
133 |     public function _handleSuccess($value): void
134 |     {
135 |         unset($this->currentPromise);
136 |         try {
137 |             $next = $this->generator->send($value);
138 |             if ($this->generator->valid()) {
139 |                 $this->nextCoroutine($next);
140 |             } else {
141 |                 $this->result->resolve($value);
142 |             }
143 |         } catch (Throwable $throwable) {
144 |             $this->result->reject($throwable);
145 |         }
146 |     }
147 | 
148 |     /**
149 |      * @internal
150 |      */
151 |     public function _handleFailure($reason): void
152 |     {
153 |         unset($this->currentPromise);
154 |         try {
155 |             $nextYield = $this->generator->throw(Create::exceptionFor($reason));
156 |             // The throw was caught, so keep iterating on the coroutine
157 |             $this->nextCoroutine($nextYield);
158 |         } catch (Throwable $throwable) {
159 |             $this->result->reject($throwable);
160 |         }
161 |     }
162 | }
163 | 


--------------------------------------------------------------------------------
/src/Create.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | final class Create
 8 | {
 9 |     /**
10 |      * Creates a promise for a value if the value is not a promise.
11 |      *
12 |      * @param mixed $value Promise or value.
13 |      */
14 |     public static function promiseFor($value): PromiseInterface
15 |     {
16 |         if ($value instanceof PromiseInterface) {
17 |             return $value;
18 |         }
19 | 
20 |         // Return a Guzzle promise that shadows the given promise.
21 |         if (is_object($value) && method_exists($value, 'then')) {
22 |             $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
23 |             $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
24 |             $promise = new Promise($wfn, $cfn);
25 |             $value->then([$promise, 'resolve'], [$promise, 'reject']);
26 | 
27 |             return $promise;
28 |         }
29 | 
30 |         return new FulfilledPromise($value);
31 |     }
32 | 
33 |     /**
34 |      * Creates a rejected promise for a reason if the reason is not a promise.
35 |      * If the provided reason is a promise, then it is returned as-is.
36 |      *
37 |      * @param mixed $reason Promise or reason.
38 |      */
39 |     public static function rejectionFor($reason): PromiseInterface
40 |     {
41 |         if ($reason instanceof PromiseInterface) {
42 |             return $reason;
43 |         }
44 | 
45 |         return new RejectedPromise($reason);
46 |     }
47 | 
48 |     /**
49 |      * Create an exception for a rejected promise value.
50 |      *
51 |      * @param mixed $reason
52 |      */
53 |     public static function exceptionFor($reason): \Throwable
54 |     {
55 |         if ($reason instanceof \Throwable) {
56 |             return $reason;
57 |         }
58 | 
59 |         return new RejectionException($reason);
60 |     }
61 | 
62 |     /**
63 |      * Returns an iterator for the given value.
64 |      *
65 |      * @param mixed $value
66 |      */
67 |     public static function iterFor($value): \Iterator
68 |     {
69 |         if ($value instanceof \Iterator) {
70 |             return $value;
71 |         }
72 | 
73 |         if (is_array($value)) {
74 |             return new \ArrayIterator($value);
75 |         }
76 | 
77 |         return new \ArrayIterator([$value]);
78 |     }
79 | }
80 | 


--------------------------------------------------------------------------------
/src/Each.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | final class Each
 8 | {
 9 |     /**
10 |      * Given an iterator that yields promises or values, returns a promise that
11 |      * is fulfilled with a null value when the iterator has been consumed or
12 |      * the aggregate promise has been fulfilled or rejected.
13 |      *
14 |      * $onFulfilled is a function that accepts the fulfilled value, iterator
15 |      * index, and the aggregate promise. The callback can invoke any necessary
16 |      * side effects and choose to resolve or reject the aggregate if needed.
17 |      *
18 |      * $onRejected is a function that accepts the rejection reason, iterator
19 |      * index, and the aggregate promise. The callback can invoke any necessary
20 |      * side effects and choose to resolve or reject the aggregate if needed.
21 |      *
22 |      * @param mixed $iterable Iterator or array to iterate over.
23 |      */
24 |     public static function of(
25 |         $iterable,
26 |         ?callable $onFulfilled = null,
27 |         ?callable $onRejected = null
28 |     ): PromiseInterface {
29 |         return (new EachPromise($iterable, [
30 |             'fulfilled' => $onFulfilled,
31 |             'rejected' => $onRejected,
32 |         ]))->promise();
33 |     }
34 | 
35 |     /**
36 |      * Like of, but only allows a certain number of outstanding promises at any
37 |      * given time.
38 |      *
39 |      * $concurrency may be an integer or a function that accepts the number of
40 |      * pending promises and returns a numeric concurrency limit value to allow
41 |      * for dynamic a concurrency size.
42 |      *
43 |      * @param mixed        $iterable
44 |      * @param int|callable $concurrency
45 |      */
46 |     public static function ofLimit(
47 |         $iterable,
48 |         $concurrency,
49 |         ?callable $onFulfilled = null,
50 |         ?callable $onRejected = null
51 |     ): PromiseInterface {
52 |         return (new EachPromise($iterable, [
53 |             'fulfilled' => $onFulfilled,
54 |             'rejected' => $onRejected,
55 |             'concurrency' => $concurrency,
56 |         ]))->promise();
57 |     }
58 | 
59 |     /**
60 |      * Like limit, but ensures that no promise in the given $iterable argument
61 |      * is rejected. If any promise is rejected, then the aggregate promise is
62 |      * rejected with the encountered rejection.
63 |      *
64 |      * @param mixed        $iterable
65 |      * @param int|callable $concurrency
66 |      */
67 |     public static function ofLimitAll(
68 |         $iterable,
69 |         $concurrency,
70 |         ?callable $onFulfilled = null
71 |     ): PromiseInterface {
72 |         return self::ofLimit(
73 |             $iterable,
74 |             $concurrency,
75 |             $onFulfilled,
76 |             function ($reason, $idx, PromiseInterface $aggregate): void {
77 |                 $aggregate->reject($reason);
78 |             }
79 |         );
80 |     }
81 | }
82 | 


--------------------------------------------------------------------------------
/src/EachPromise.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | declare(strict_types=1);
  4 | 
  5 | namespace GuzzleHttp\Promise;
  6 | 
  7 | /**
  8 |  * Represents a promise that iterates over many promises and invokes
  9 |  * side-effect functions in the process.
 10 |  *
 11 |  * @final
 12 |  */
 13 | class EachPromise implements PromisorInterface
 14 | {
 15 |     private $pending = [];
 16 | 
 17 |     private $nextPendingIndex = 0;
 18 | 
 19 |     /** @var \Iterator|null */
 20 |     private $iterable;
 21 | 
 22 |     /** @var callable|int|null */
 23 |     private $concurrency;
 24 | 
 25 |     /** @var callable|null */
 26 |     private $onFulfilled;
 27 | 
 28 |     /** @var callable|null */
 29 |     private $onRejected;
 30 | 
 31 |     /** @var Promise|null */
 32 |     private $aggregate;
 33 | 
 34 |     /** @var bool|null */
 35 |     private $mutex;
 36 | 
 37 |     /**
 38 |      * Configuration hash can include the following key value pairs:
 39 |      *
 40 |      * - fulfilled: (callable) Invoked when a promise fulfills. The function
 41 |      *   is invoked with three arguments: the fulfillment value, the index
 42 |      *   position from the iterable list of the promise, and the aggregate
 43 |      *   promise that manages all of the promises. The aggregate promise may
 44 |      *   be resolved from within the callback to short-circuit the promise.
 45 |      * - rejected: (callable) Invoked when a promise is rejected. The
 46 |      *   function is invoked with three arguments: the rejection reason, the
 47 |      *   index position from the iterable list of the promise, and the
 48 |      *   aggregate promise that manages all of the promises. The aggregate
 49 |      *   promise may be resolved from within the callback to short-circuit
 50 |      *   the promise.
 51 |      * - concurrency: (integer) Pass this configuration option to limit the
 52 |      *   allowed number of outstanding concurrently executing promises,
 53 |      *   creating a capped pool of promises. There is no limit by default.
 54 |      *
 55 |      * @param mixed $iterable Promises or values to iterate.
 56 |      * @param array $config   Configuration options
 57 |      */
 58 |     public function __construct($iterable, array $config = [])
 59 |     {
 60 |         $this->iterable = Create::iterFor($iterable);
 61 | 
 62 |         if (isset($config['concurrency'])) {
 63 |             $this->concurrency = $config['concurrency'];
 64 |         }
 65 | 
 66 |         if (isset($config['fulfilled'])) {
 67 |             $this->onFulfilled = $config['fulfilled'];
 68 |         }
 69 | 
 70 |         if (isset($config['rejected'])) {
 71 |             $this->onRejected = $config['rejected'];
 72 |         }
 73 |     }
 74 | 
 75 |     /** @psalm-suppress InvalidNullableReturnType */
 76 |     public function promise(): PromiseInterface
 77 |     {
 78 |         if ($this->aggregate) {
 79 |             return $this->aggregate;
 80 |         }
 81 | 
 82 |         try {
 83 |             $this->createPromise();
 84 |             /** @psalm-assert Promise $this->aggregate */
 85 |             $this->iterable->rewind();
 86 |             $this->refillPending();
 87 |         } catch (\Throwable $e) {
 88 |             $this->aggregate->reject($e);
 89 |         }
 90 | 
 91 |         /**
 92 |          * @psalm-suppress NullableReturnStatement
 93 |          */
 94 |         return $this->aggregate;
 95 |     }
 96 | 
 97 |     private function createPromise(): void
 98 |     {
 99 |         $this->mutex = false;
100 |         $this->aggregate = new Promise(function (): void {
101 |             if ($this->checkIfFinished()) {
102 |                 return;
103 |             }
104 |             reset($this->pending);
105 |             // Consume a potentially fluctuating list of promises while
106 |             // ensuring that indexes are maintained (precluding array_shift).
107 |             while ($promise = current($this->pending)) {
108 |                 next($this->pending);
109 |                 $promise->wait();
110 |                 if (Is::settled($this->aggregate)) {
111 |                     return;
112 |                 }
113 |             }
114 |         });
115 | 
116 |         // Clear the references when the promise is resolved.
117 |         $clearFn = function (): void {
118 |             $this->iterable = $this->concurrency = $this->pending = null;
119 |             $this->onFulfilled = $this->onRejected = null;
120 |             $this->nextPendingIndex = 0;
121 |         };
122 | 
123 |         $this->aggregate->then($clearFn, $clearFn);
124 |     }
125 | 
126 |     private function refillPending(): void
127 |     {
128 |         if (!$this->concurrency) {
129 |             // Add all pending promises.
130 |             while ($this->addPending() && $this->advanceIterator()) {
131 |             }
132 | 
133 |             return;
134 |         }
135 | 
136 |         // Add only up to N pending promises.
137 |         $concurrency = is_callable($this->concurrency)
138 |             ? ($this->concurrency)(count($this->pending))
139 |             : $this->concurrency;
140 |         $concurrency = max($concurrency - count($this->pending), 0);
141 |         // Concurrency may be set to 0 to disallow new promises.
142 |         if (!$concurrency) {
143 |             return;
144 |         }
145 |         // Add the first pending promise.
146 |         $this->addPending();
147 |         // Note this is special handling for concurrency=1 so that we do
148 |         // not advance the iterator after adding the first promise. This
149 |         // helps work around issues with generators that might not have the
150 |         // next value to yield until promise callbacks are called.
151 |         while (--$concurrency
152 |             && $this->advanceIterator()
153 |             && $this->addPending()) {
154 |         }
155 |     }
156 | 
157 |     private function addPending(): bool
158 |     {
159 |         if (!$this->iterable || !$this->iterable->valid()) {
160 |             return false;
161 |         }
162 | 
163 |         $promise = Create::promiseFor($this->iterable->current());
164 |         $key = $this->iterable->key();
165 | 
166 |         // Iterable keys may not be unique, so we use a counter to
167 |         // guarantee uniqueness
168 |         $idx = $this->nextPendingIndex++;
169 | 
170 |         $this->pending[$idx] = $promise->then(
171 |             function ($value) use ($idx, $key): void {
172 |                 if ($this->onFulfilled) {
173 |                     ($this->onFulfilled)(
174 |                         $value,
175 |                         $key,
176 |                         $this->aggregate
177 |                     );
178 |                 }
179 |                 $this->step($idx);
180 |             },
181 |             function ($reason) use ($idx, $key): void {
182 |                 if ($this->onRejected) {
183 |                     ($this->onRejected)(
184 |                         $reason,
185 |                         $key,
186 |                         $this->aggregate
187 |                     );
188 |                 }
189 |                 $this->step($idx);
190 |             }
191 |         );
192 | 
193 |         return true;
194 |     }
195 | 
196 |     private function advanceIterator(): bool
197 |     {
198 |         // Place a lock on the iterator so that we ensure to not recurse,
199 |         // preventing fatal generator errors.
200 |         if ($this->mutex) {
201 |             return false;
202 |         }
203 | 
204 |         $this->mutex = true;
205 | 
206 |         try {
207 |             $this->iterable->next();
208 |             $this->mutex = false;
209 | 
210 |             return true;
211 |         } catch (\Throwable $e) {
212 |             $this->aggregate->reject($e);
213 |             $this->mutex = false;
214 | 
215 |             return false;
216 |         }
217 |     }
218 | 
219 |     private function step(int $idx): void
220 |     {
221 |         // If the promise was already resolved, then ignore this step.
222 |         if (Is::settled($this->aggregate)) {
223 |             return;
224 |         }
225 | 
226 |         unset($this->pending[$idx]);
227 | 
228 |         // Only refill pending promises if we are not locked, preventing the
229 |         // EachPromise to recursively invoke the provided iterator, which
230 |         // cause a fatal error: "Cannot resume an already running generator"
231 |         if ($this->advanceIterator() && !$this->checkIfFinished()) {
232 |             // Add more pending promises if possible.
233 |             $this->refillPending();
234 |         }
235 |     }
236 | 
237 |     private function checkIfFinished(): bool
238 |     {
239 |         if (!$this->pending && !$this->iterable->valid()) {
240 |             // Resolve the promise if there's nothing left to do.
241 |             $this->aggregate->resolve(null);
242 | 
243 |             return true;
244 |         }
245 | 
246 |         return false;
247 |     }
248 | }
249 | 


--------------------------------------------------------------------------------
/src/FulfilledPromise.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * A promise that has been fulfilled.
 9 |  *
10 |  * Thenning off of this promise will invoke the onFulfilled callback
11 |  * immediately and ignore other callbacks.
12 |  *
13 |  * @final
14 |  */
15 | class FulfilledPromise implements PromiseInterface
16 | {
17 |     private $value;
18 | 
19 |     /**
20 |      * @param mixed $value
21 |      */
22 |     public function __construct($value)
23 |     {
24 |         if (is_object($value) && method_exists($value, 'then')) {
25 |             throw new \InvalidArgumentException(
26 |                 'You cannot create a FulfilledPromise with a promise.'
27 |             );
28 |         }
29 | 
30 |         $this->value = $value;
31 |     }
32 | 
33 |     public function then(
34 |         ?callable $onFulfilled = null,
35 |         ?callable $onRejected = null
36 |     ): PromiseInterface {
37 |         // Return itself if there is no onFulfilled function.
38 |         if (!$onFulfilled) {
39 |             return $this;
40 |         }
41 | 
42 |         $queue = Utils::queue();
43 |         $p = new Promise([$queue, 'run']);
44 |         $value = $this->value;
45 |         $queue->add(static function () use ($p, $value, $onFulfilled): void {
46 |             if (Is::pending($p)) {
47 |                 try {
48 |                     $p->resolve($onFulfilled($value));
49 |                 } catch (\Throwable $e) {
50 |                     $p->reject($e);
51 |                 }
52 |             }
53 |         });
54 | 
55 |         return $p;
56 |     }
57 | 
58 |     public function otherwise(callable $onRejected): PromiseInterface
59 |     {
60 |         return $this->then(null, $onRejected);
61 |     }
62 | 
63 |     public function wait(bool $unwrap = true)
64 |     {
65 |         return $unwrap ? $this->value : null;
66 |     }
67 | 
68 |     public function getState(): string
69 |     {
70 |         return self::FULFILLED;
71 |     }
72 | 
73 |     public function resolve($value): void
74 |     {
75 |         if ($value !== $this->value) {
76 |             throw new \LogicException('Cannot resolve a fulfilled promise');
77 |         }
78 |     }
79 | 
80 |     public function reject($reason): void
81 |     {
82 |         throw new \LogicException('Cannot reject a fulfilled promise');
83 |     }
84 | 
85 |     public function cancel(): void
86 |     {
87 |         // pass
88 |     }
89 | }
90 | 


--------------------------------------------------------------------------------
/src/Is.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | final class Is
 8 | {
 9 |     /**
10 |      * Returns true if a promise is pending.
11 |      */
12 |     public static function pending(PromiseInterface $promise): bool
13 |     {
14 |         return $promise->getState() === PromiseInterface::PENDING;
15 |     }
16 | 
17 |     /**
18 |      * Returns true if a promise is fulfilled or rejected.
19 |      */
20 |     public static function settled(PromiseInterface $promise): bool
21 |     {
22 |         return $promise->getState() !== PromiseInterface::PENDING;
23 |     }
24 | 
25 |     /**
26 |      * Returns true if a promise is fulfilled.
27 |      */
28 |     public static function fulfilled(PromiseInterface $promise): bool
29 |     {
30 |         return $promise->getState() === PromiseInterface::FULFILLED;
31 |     }
32 | 
33 |     /**
34 |      * Returns true if a promise is rejected.
35 |      */
36 |     public static function rejected(PromiseInterface $promise): bool
37 |     {
38 |         return $promise->getState() === PromiseInterface::REJECTED;
39 |     }
40 | }
41 | 


--------------------------------------------------------------------------------
/src/Promise.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | declare(strict_types=1);
  4 | 
  5 | namespace GuzzleHttp\Promise;
  6 | 
  7 | /**
  8 |  * Promises/A+ implementation that avoids recursion when possible.
  9 |  *
 10 |  * @see https://promisesaplus.com/
 11 |  *
 12 |  * @final
 13 |  */
 14 | class Promise implements PromiseInterface
 15 | {
 16 |     private $state = self::PENDING;
 17 |     private $result;
 18 |     private $cancelFn;
 19 |     private $waitFn;
 20 |     private $waitList;
 21 |     private $handlers = [];
 22 | 
 23 |     /**
 24 |      * @param callable $waitFn   Fn that when invoked resolves the promise.
 25 |      * @param callable $cancelFn Fn that when invoked cancels the promise.
 26 |      */
 27 |     public function __construct(
 28 |         ?callable $waitFn = null,
 29 |         ?callable $cancelFn = null
 30 |     ) {
 31 |         $this->waitFn = $waitFn;
 32 |         $this->cancelFn = $cancelFn;
 33 |     }
 34 | 
 35 |     public function then(
 36 |         ?callable $onFulfilled = null,
 37 |         ?callable $onRejected = null
 38 |     ): PromiseInterface {
 39 |         if ($this->state === self::PENDING) {
 40 |             $p = new Promise(null, [$this, 'cancel']);
 41 |             $this->handlers[] = [$p, $onFulfilled, $onRejected];
 42 |             $p->waitList = $this->waitList;
 43 |             $p->waitList[] = $this;
 44 | 
 45 |             return $p;
 46 |         }
 47 | 
 48 |         // Return a fulfilled promise and immediately invoke any callbacks.
 49 |         if ($this->state === self::FULFILLED) {
 50 |             $promise = Create::promiseFor($this->result);
 51 | 
 52 |             return $onFulfilled ? $promise->then($onFulfilled) : $promise;
 53 |         }
 54 | 
 55 |         // It's either cancelled or rejected, so return a rejected promise
 56 |         // and immediately invoke any callbacks.
 57 |         $rejection = Create::rejectionFor($this->result);
 58 | 
 59 |         return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
 60 |     }
 61 | 
 62 |     public function otherwise(callable $onRejected): PromiseInterface
 63 |     {
 64 |         return $this->then(null, $onRejected);
 65 |     }
 66 | 
 67 |     public function wait(bool $unwrap = true)
 68 |     {
 69 |         $this->waitIfPending();
 70 | 
 71 |         if ($this->result instanceof PromiseInterface) {
 72 |             return $this->result->wait($unwrap);
 73 |         }
 74 |         if ($unwrap) {
 75 |             if ($this->state === self::FULFILLED) {
 76 |                 return $this->result;
 77 |             }
 78 |             // It's rejected so "unwrap" and throw an exception.
 79 |             throw Create::exceptionFor($this->result);
 80 |         }
 81 |     }
 82 | 
 83 |     public function getState(): string
 84 |     {
 85 |         return $this->state;
 86 |     }
 87 | 
 88 |     public function cancel(): void
 89 |     {
 90 |         if ($this->state !== self::PENDING) {
 91 |             return;
 92 |         }
 93 | 
 94 |         $this->waitFn = $this->waitList = null;
 95 | 
 96 |         if ($this->cancelFn) {
 97 |             $fn = $this->cancelFn;
 98 |             $this->cancelFn = null;
 99 |             try {
100 |                 $fn();
101 |             } catch (\Throwable $e) {
102 |                 $this->reject($e);
103 |             }
104 |         }
105 | 
106 |         // Reject the promise only if it wasn't rejected in a then callback.
107 |         /** @psalm-suppress RedundantCondition */
108 |         if ($this->state === self::PENDING) {
109 |             $this->reject(new CancellationException('Promise has been cancelled'));
110 |         }
111 |     }
112 | 
113 |     public function resolve($value): void
114 |     {
115 |         $this->settle(self::FULFILLED, $value);
116 |     }
117 | 
118 |     public function reject($reason): void
119 |     {
120 |         $this->settle(self::REJECTED, $reason);
121 |     }
122 | 
123 |     private function settle(string $state, $value): void
124 |     {
125 |         if ($this->state !== self::PENDING) {
126 |             // Ignore calls with the same resolution.
127 |             if ($state === $this->state && $value === $this->result) {
128 |                 return;
129 |             }
130 |             throw $this->state === $state
131 |                 ? new \LogicException("The promise is already {$state}.")
132 |                 : new \LogicException("Cannot change a {$this->state} promise to {$state}");
133 |         }
134 | 
135 |         if ($value === $this) {
136 |             throw new \LogicException('Cannot fulfill or reject a promise with itself');
137 |         }
138 | 
139 |         // Clear out the state of the promise but stash the handlers.
140 |         $this->state = $state;
141 |         $this->result = $value;
142 |         $handlers = $this->handlers;
143 |         $this->handlers = null;
144 |         $this->waitList = $this->waitFn = null;
145 |         $this->cancelFn = null;
146 | 
147 |         if (!$handlers) {
148 |             return;
149 |         }
150 | 
151 |         // If the value was not a settled promise or a thenable, then resolve
152 |         // it in the task queue using the correct ID.
153 |         if (!is_object($value) || !method_exists($value, 'then')) {
154 |             $id = $state === self::FULFILLED ? 1 : 2;
155 |             // It's a success, so resolve the handlers in the queue.
156 |             Utils::queue()->add(static function () use ($id, $value, $handlers): void {
157 |                 foreach ($handlers as $handler) {
158 |                     self::callHandler($id, $value, $handler);
159 |                 }
160 |             });
161 |         } elseif ($value instanceof Promise && Is::pending($value)) {
162 |             // We can just merge our handlers onto the next promise.
163 |             $value->handlers = array_merge($value->handlers, $handlers);
164 |         } else {
165 |             // Resolve the handlers when the forwarded promise is resolved.
166 |             $value->then(
167 |                 static function ($value) use ($handlers): void {
168 |                     foreach ($handlers as $handler) {
169 |                         self::callHandler(1, $value, $handler);
170 |                     }
171 |                 },
172 |                 static function ($reason) use ($handlers): void {
173 |                     foreach ($handlers as $handler) {
174 |                         self::callHandler(2, $reason, $handler);
175 |                     }
176 |                 }
177 |             );
178 |         }
179 |     }
180 | 
181 |     /**
182 |      * Call a stack of handlers using a specific callback index and value.
183 |      *
184 |      * @param int   $index   1 (resolve) or 2 (reject).
185 |      * @param mixed $value   Value to pass to the callback.
186 |      * @param array $handler Array of handler data (promise and callbacks).
187 |      */
188 |     private static function callHandler(int $index, $value, array $handler): void
189 |     {
190 |         /** @var PromiseInterface $promise */
191 |         $promise = $handler[0];
192 | 
193 |         // The promise may have been cancelled or resolved before placing
194 |         // this thunk in the queue.
195 |         if (Is::settled($promise)) {
196 |             return;
197 |         }
198 | 
199 |         try {
200 |             if (isset($handler[$index])) {
201 |                 /*
202 |                  * If $f throws an exception, then $handler will be in the exception
203 |                  * stack trace. Since $handler contains a reference to the callable
204 |                  * itself we get a circular reference. We clear the $handler
205 |                  * here to avoid that memory leak.
206 |                  */
207 |                 $f = $handler[$index];
208 |                 unset($handler);
209 |                 $promise->resolve($f($value));
210 |             } elseif ($index === 1) {
211 |                 // Forward resolution values as-is.
212 |                 $promise->resolve($value);
213 |             } else {
214 |                 // Forward rejections down the chain.
215 |                 $promise->reject($value);
216 |             }
217 |         } catch (\Throwable $reason) {
218 |             $promise->reject($reason);
219 |         }
220 |     }
221 | 
222 |     private function waitIfPending(): void
223 |     {
224 |         if ($this->state !== self::PENDING) {
225 |             return;
226 |         } elseif ($this->waitFn) {
227 |             $this->invokeWaitFn();
228 |         } elseif ($this->waitList) {
229 |             $this->invokeWaitList();
230 |         } else {
231 |             // If there's no wait function, then reject the promise.
232 |             $this->reject('Cannot wait on a promise that has '
233 |                 .'no internal wait function. You must provide a wait '
234 |                 .'function when constructing the promise to be able to '
235 |                 .'wait on a promise.');
236 |         }
237 | 
238 |         Utils::queue()->run();
239 | 
240 |         /** @psalm-suppress RedundantCondition */
241 |         if ($this->state === self::PENDING) {
242 |             $this->reject('Invoking the wait callback did not resolve the promise');
243 |         }
244 |     }
245 | 
246 |     private function invokeWaitFn(): void
247 |     {
248 |         try {
249 |             $wfn = $this->waitFn;
250 |             $this->waitFn = null;
251 |             $wfn(true);
252 |         } catch (\Throwable $reason) {
253 |             if ($this->state === self::PENDING) {
254 |                 // The promise has not been resolved yet, so reject the promise
255 |                 // with the exception.
256 |                 $this->reject($reason);
257 |             } else {
258 |                 // The promise was already resolved, so there's a problem in
259 |                 // the application.
260 |                 throw $reason;
261 |             }
262 |         }
263 |     }
264 | 
265 |     private function invokeWaitList(): void
266 |     {
267 |         $waitList = $this->waitList;
268 |         $this->waitList = null;
269 | 
270 |         foreach ($waitList as $result) {
271 |             do {
272 |                 $result->waitIfPending();
273 |                 $result = $result->result;
274 |             } while ($result instanceof Promise);
275 | 
276 |             if ($result instanceof PromiseInterface) {
277 |                 $result->wait(false);
278 |             }
279 |         }
280 |     }
281 | }
282 | 


--------------------------------------------------------------------------------
/src/PromiseInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * A promise represents the eventual result of an asynchronous operation.
 9 |  *
10 |  * The primary way of interacting with a promise is through its then method,
11 |  * which registers callbacks to receive either a promise’s eventual value or
12 |  * the reason why the promise cannot be fulfilled.
13 |  *
14 |  * @see https://promisesaplus.com/
15 |  */
16 | interface PromiseInterface
17 | {
18 |     public const PENDING = 'pending';
19 |     public const FULFILLED = 'fulfilled';
20 |     public const REJECTED = 'rejected';
21 | 
22 |     /**
23 |      * Appends fulfillment and rejection handlers to the promise, and returns
24 |      * a new promise resolving to the return value of the called handler.
25 |      *
26 |      * @param callable $onFulfilled Invoked when the promise fulfills.
27 |      * @param callable $onRejected  Invoked when the promise is rejected.
28 |      */
29 |     public function then(
30 |         ?callable $onFulfilled = null,
31 |         ?callable $onRejected = null
32 |     ): PromiseInterface;
33 | 
34 |     /**
35 |      * Appends a rejection handler callback to the promise, and returns a new
36 |      * promise resolving to the return value of the callback if it is called,
37 |      * or to its original fulfillment value if the promise is instead
38 |      * fulfilled.
39 |      *
40 |      * @param callable $onRejected Invoked when the promise is rejected.
41 |      */
42 |     public function otherwise(callable $onRejected): PromiseInterface;
43 | 
44 |     /**
45 |      * Get the state of the promise ("pending", "rejected", or "fulfilled").
46 |      *
47 |      * The three states can be checked against the constants defined on
48 |      * PromiseInterface: PENDING, FULFILLED, and REJECTED.
49 |      */
50 |     public function getState(): string;
51 | 
52 |     /**
53 |      * Resolve the promise with the given value.
54 |      *
55 |      * @param mixed $value
56 |      *
57 |      * @throws \RuntimeException if the promise is already resolved.
58 |      */
59 |     public function resolve($value): void;
60 | 
61 |     /**
62 |      * Reject the promise with the given reason.
63 |      *
64 |      * @param mixed $reason
65 |      *
66 |      * @throws \RuntimeException if the promise is already resolved.
67 |      */
68 |     public function reject($reason): void;
69 | 
70 |     /**
71 |      * Cancels the promise if possible.
72 |      *
73 |      * @see https://github.com/promises-aplus/cancellation-spec/issues/7
74 |      */
75 |     public function cancel(): void;
76 | 
77 |     /**
78 |      * Waits until the promise completes if possible.
79 |      *
80 |      * Pass $unwrap as true to unwrap the result of the promise, either
81 |      * returning the resolved value or throwing the rejected exception.
82 |      *
83 |      * If the promise cannot be waited on, then the promise will be rejected.
84 |      *
85 |      * @return mixed
86 |      *
87 |      * @throws \LogicException if the promise has no wait function or if the
88 |      *                         promise does not settle after waiting.
89 |      */
90 |     public function wait(bool $unwrap = true);
91 | }
92 | 


--------------------------------------------------------------------------------
/src/PromisorInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * Interface used with classes that return a promise.
 9 |  */
10 | interface PromisorInterface
11 | {
12 |     /**
13 |      * Returns a promise.
14 |      */
15 |     public function promise(): PromiseInterface;
16 | }
17 | 


--------------------------------------------------------------------------------
/src/RejectedPromise.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * A promise that has been rejected.
 9 |  *
10 |  * Thenning off of this promise will invoke the onRejected callback
11 |  * immediately and ignore other callbacks.
12 |  *
13 |  * @final
14 |  */
15 | class RejectedPromise implements PromiseInterface
16 | {
17 |     private $reason;
18 | 
19 |     /**
20 |      * @param mixed $reason
21 |      */
22 |     public function __construct($reason)
23 |     {
24 |         if (is_object($reason) && method_exists($reason, 'then')) {
25 |             throw new \InvalidArgumentException(
26 |                 'You cannot create a RejectedPromise with a promise.'
27 |             );
28 |         }
29 | 
30 |         $this->reason = $reason;
31 |     }
32 | 
33 |     public function then(
34 |         ?callable $onFulfilled = null,
35 |         ?callable $onRejected = null
36 |     ): PromiseInterface {
37 |         // If there's no onRejected callback then just return self.
38 |         if (!$onRejected) {
39 |             return $this;
40 |         }
41 | 
42 |         $queue = Utils::queue();
43 |         $reason = $this->reason;
44 |         $p = new Promise([$queue, 'run']);
45 |         $queue->add(static function () use ($p, $reason, $onRejected): void {
46 |             if (Is::pending($p)) {
47 |                 try {
48 |                     // Return a resolved promise if onRejected does not throw.
49 |                     $p->resolve($onRejected($reason));
50 |                 } catch (\Throwable $e) {
51 |                     // onRejected threw, so return a rejected promise.
52 |                     $p->reject($e);
53 |                 }
54 |             }
55 |         });
56 | 
57 |         return $p;
58 |     }
59 | 
60 |     public function otherwise(callable $onRejected): PromiseInterface
61 |     {
62 |         return $this->then(null, $onRejected);
63 |     }
64 | 
65 |     public function wait(bool $unwrap = true)
66 |     {
67 |         if ($unwrap) {
68 |             throw Create::exceptionFor($this->reason);
69 |         }
70 | 
71 |         return null;
72 |     }
73 | 
74 |     public function getState(): string
75 |     {
76 |         return self::REJECTED;
77 |     }
78 | 
79 |     public function resolve($value): void
80 |     {
81 |         throw new \LogicException('Cannot resolve a rejected promise');
82 |     }
83 | 
84 |     public function reject($reason): void
85 |     {
86 |         if ($reason !== $this->reason) {
87 |             throw new \LogicException('Cannot reject a rejected promise');
88 |         }
89 |     }
90 | 
91 |     public function cancel(): void
92 |     {
93 |         // pass
94 |     }
95 | }
96 | 


--------------------------------------------------------------------------------
/src/RejectionException.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * A special exception that is thrown when waiting on a rejected promise.
 9 |  *
10 |  * The reason value is available via the getReason() method.
11 |  */
12 | class RejectionException extends \RuntimeException
13 | {
14 |     /** @var mixed Rejection reason. */
15 |     private $reason;
16 | 
17 |     /**
18 |      * @param mixed       $reason      Rejection reason.
19 |      * @param string|null $description Optional description.
20 |      */
21 |     public function __construct($reason, ?string $description = null)
22 |     {
23 |         $this->reason = $reason;
24 | 
25 |         $message = 'The promise was rejected';
26 | 
27 |         if ($description) {
28 |             $message .= ' with reason: '.$description;
29 |         } elseif (is_string($reason)
30 |             || (is_object($reason) && method_exists($reason, '__toString'))
31 |         ) {
32 |             $message .= ' with reason: '.$this->reason;
33 |         } elseif ($reason instanceof \JsonSerializable) {
34 |             $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT);
35 |         }
36 | 
37 |         parent::__construct($message);
38 |     }
39 | 
40 |     /**
41 |      * Returns the rejection reason.
42 |      *
43 |      * @return mixed
44 |      */
45 |     public function getReason()
46 |     {
47 |         return $this->reason;
48 |     }
49 | }
50 | 


--------------------------------------------------------------------------------
/src/TaskQueue.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | /**
 8 |  * A task queue that executes tasks in a FIFO order.
 9 |  *
10 |  * This task queue class is used to settle promises asynchronously and
11 |  * maintains a constant stack size. You can use the task queue asynchronously
12 |  * by calling the `run()` function of the global task queue in an event loop.
13 |  *
14 |  *     GuzzleHttp\Promise\Utils::queue()->run();
15 |  *
16 |  * @final
17 |  */
18 | class TaskQueue implements TaskQueueInterface
19 | {
20 |     private $enableShutdown = true;
21 |     private $queue = [];
22 | 
23 |     public function __construct(bool $withShutdown = true)
24 |     {
25 |         if ($withShutdown) {
26 |             register_shutdown_function(function (): void {
27 |                 if ($this->enableShutdown) {
28 |                     // Only run the tasks if an E_ERROR didn't occur.
29 |                     $err = error_get_last();
30 |                     if (!$err || ($err['type'] ^ E_ERROR)) {
31 |                         $this->run();
32 |                     }
33 |                 }
34 |             });
35 |         }
36 |     }
37 | 
38 |     public function isEmpty(): bool
39 |     {
40 |         return !$this->queue;
41 |     }
42 | 
43 |     public function add(callable $task): void
44 |     {
45 |         $this->queue[] = $task;
46 |     }
47 | 
48 |     public function run(): void
49 |     {
50 |         while ($task = array_shift($this->queue)) {
51 |             /** @var callable $task */
52 |             $task();
53 |         }
54 |     }
55 | 
56 |     /**
57 |      * The task queue will be run and exhausted by default when the process
58 |      * exits IFF the exit is not the result of a PHP E_ERROR error.
59 |      *
60 |      * You can disable running the automatic shutdown of the queue by calling
61 |      * this function. If you disable the task queue shutdown process, then you
62 |      * MUST either run the task queue (as a result of running your event loop
63 |      * or manually using the run() method) or wait on each outstanding promise.
64 |      *
65 |      * Note: This shutdown will occur before any destructors are triggered.
66 |      */
67 |     public function disableShutdown(): void
68 |     {
69 |         $this->enableShutdown = false;
70 |     }
71 | }
72 | 


--------------------------------------------------------------------------------
/src/TaskQueueInterface.php:
--------------------------------------------------------------------------------
 1 | <?php
 2 | 
 3 | declare(strict_types=1);
 4 | 
 5 | namespace GuzzleHttp\Promise;
 6 | 
 7 | interface TaskQueueInterface
 8 | {
 9 |     /**
10 |      * Returns true if the queue is empty.
11 |      */
12 |     public function isEmpty(): bool;
13 | 
14 |     /**
15 |      * Adds a task to the queue that will be executed the next time run is
16 |      * called.
17 |      */
18 |     public function add(callable $task): void;
19 | 
20 |     /**
21 |      * Execute all of the pending task in the queue.
22 |      */
23 |     public function run(): void;
24 | }
25 | 


--------------------------------------------------------------------------------
/src/Utils.php:
--------------------------------------------------------------------------------
  1 | <?php
  2 | 
  3 | declare(strict_types=1);
  4 | 
  5 | namespace GuzzleHttp\Promise;
  6 | 
  7 | final class Utils
  8 | {
  9 |     /**
 10 |      * Get the global task queue used for promise resolution.
 11 |      *
 12 |      * This task queue MUST be run in an event loop in order for promises to be
 13 |      * settled asynchronously. It will be automatically run when synchronously
 14 |      * waiting on a promise.
 15 |      *
 16 |      * <code>
 17 |      * while ($eventLoop->isRunning()) {
 18 |      *     GuzzleHttp\Promise\Utils::queue()->run();
 19 |      * }
 20 |      * </code>
 21 |      *
 22 |      * @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
 23 |      */
 24 |     public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface
 25 |     {
 26 |         static $queue;
 27 | 
 28 |         if ($assign) {
 29 |             $queue = $assign;
 30 |         } elseif (!$queue) {
 31 |             $queue = new TaskQueue();
 32 |         }
 33 | 
 34 |         return $queue;
 35 |     }
 36 | 
 37 |     /**
 38 |      * Adds a function to run in the task queue when it is next `run()` and
 39 |      * returns a promise that is fulfilled or rejected with the result.
 40 |      *
 41 |      * @param callable $task Task function to run.
 42 |      */
 43 |     public static function task(callable $task): PromiseInterface
 44 |     {
 45 |         $queue = self::queue();
 46 |         $promise = new Promise([$queue, 'run']);
 47 |         $queue->add(function () use ($task, $promise): void {
 48 |             try {
 49 |                 if (Is::pending($promise)) {
 50 |                     $promise->resolve($task());
 51 |                 }
 52 |             } catch (\Throwable $e) {
 53 |                 $promise->reject($e);
 54 |             }
 55 |         });
 56 | 
 57 |         return $promise;
 58 |     }
 59 | 
 60 |     /**
 61 |      * Synchronously waits on a promise to resolve and returns an inspection
 62 |      * state array.
 63 |      *
 64 |      * Returns a state associative array containing a "state" key mapping to a
 65 |      * valid promise state. If the state of the promise is "fulfilled", the
 66 |      * array will contain a "value" key mapping to the fulfilled value of the
 67 |      * promise. If the promise is rejected, the array will contain a "reason"
 68 |      * key mapping to the rejection reason of the promise.
 69 |      *
 70 |      * @param PromiseInterface $promise Promise or value.
 71 |      */
 72 |     public static function inspect(PromiseInterface $promise): array
 73 |     {
 74 |         try {
 75 |             return [
 76 |                 'state' => PromiseInterface::FULFILLED,
 77 |                 'value' => $promise->wait(),
 78 |             ];
 79 |         } catch (RejectionException $e) {
 80 |             return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
 81 |         } catch (\Throwable $e) {
 82 |             return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
 83 |         }
 84 |     }
 85 | 
 86 |     /**
 87 |      * Waits on all of the provided promises, but does not unwrap rejected
 88 |      * promises as thrown exception.
 89 |      *
 90 |      * Returns an array of inspection state arrays.
 91 |      *
 92 |      * @see inspect for the inspection state array format.
 93 |      *
 94 |      * @param PromiseInterface[] $promises Traversable of promises to wait upon.
 95 |      */
 96 |     public static function inspectAll($promises): array
 97 |     {
 98 |         $results = [];
 99 |         foreach ($promises as $key => $promise) {
100 |             $results[$key] = self::inspect($promise);
101 |         }
102 | 
103 |         return $results;
104 |     }
105 | 
106 |     /**
107 |      * Waits on all of the provided promises and returns the fulfilled values.
108 |      *
109 |      * Returns an array that contains the value of each promise (in the same
110 |      * order the promises were provided). An exception is thrown if any of the
111 |      * promises are rejected.
112 |      *
113 |      * @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
114 |      *
115 |      * @throws \Throwable on error
116 |      */
117 |     public static function unwrap($promises): array
118 |     {
119 |         $results = [];
120 |         foreach ($promises as $key => $promise) {
121 |             $results[$key] = $promise->wait();
122 |         }
123 | 
124 |         return $results;
125 |     }
126 | 
127 |     /**
128 |      * Given an array of promises, return a promise that is fulfilled when all
129 |      * the items in the array are fulfilled.
130 |      *
131 |      * The promise's fulfillment value is an array with fulfillment values at
132 |      * respective positions to the original array. If any promise in the array
133 |      * rejects, the returned promise is rejected with the rejection reason.
134 |      *
135 |      * @param mixed $promises  Promises or values.
136 |      * @param bool  $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
137 |      */
138 |     public static function all($promises, bool $recursive = false): PromiseInterface
139 |     {
140 |         $results = [];
141 |         $promise = Each::of(
142 |             $promises,
143 |             function ($value, $idx) use (&$results): void {
144 |                 $results[$idx] = $value;
145 |             },
146 |             function ($reason, $idx, Promise $aggregate): void {
147 |                 if (Is::pending($aggregate)) {
148 |                     $aggregate->reject($reason);
149 |                 }
150 |             }
151 |         )->then(function () use (&$results) {
152 |             ksort($results);
153 | 
154 |             return $results;
155 |         });
156 | 
157 |         if (true === $recursive) {
158 |             $promise = $promise->then(function ($results) use ($recursive, &$promises) {
159 |                 foreach ($promises as $promise) {
160 |                     if (Is::pending($promise)) {
161 |                         return self::all($promises, $recursive);
162 |                     }
163 |                 }
164 | 
165 |                 return $results;
166 |             });
167 |         }
168 | 
169 |         return $promise;
170 |     }
171 | 
172 |     /**
173 |      * Initiate a competitive race between multiple promises or values (values
174 |      * will become immediately fulfilled promises).
175 |      *
176 |      * When count amount of promises have been fulfilled, the returned promise
177 |      * is fulfilled with an array that contains the fulfillment values of the
178 |      * winners in order of resolution.
179 |      *
180 |      * This promise is rejected with a {@see AggregateException} if the number
181 |      * of fulfilled promises is less than the desired $count.
182 |      *
183 |      * @param int   $count    Total number of promises.
184 |      * @param mixed $promises Promises or values.
185 |      */
186 |     public static function some(int $count, $promises): PromiseInterface
187 |     {
188 |         $results = [];
189 |         $rejections = [];
190 | 
191 |         return Each::of(
192 |             $promises,
193 |             function ($value, $idx, PromiseInterface $p) use (&$results, $count): void {
194 |                 if (Is::settled($p)) {
195 |                     return;
196 |                 }
197 |                 $results[$idx] = $value;
198 |                 if (count($results) >= $count) {
199 |                     $p->resolve(null);
200 |                 }
201 |             },
202 |             function ($reason) use (&$rejections): void {
203 |                 $rejections[] = $reason;
204 |             }
205 |         )->then(
206 |             function () use (&$results, &$rejections, $count) {
207 |                 if (count($results) !== $count) {
208 |                     throw new AggregateException(
209 |                         'Not enough promises to fulfill count',
210 |                         $rejections
211 |                     );
212 |                 }
213 |                 ksort($results);
214 | 
215 |                 return array_values($results);
216 |             }
217 |         );
218 |     }
219 | 
220 |     /**
221 |      * Like some(), with 1 as count. However, if the promise fulfills, the
222 |      * fulfillment value is not an array of 1 but the value directly.
223 |      *
224 |      * @param mixed $promises Promises or values.
225 |      */
226 |     public static function any($promises): PromiseInterface
227 |     {
228 |         return self::some(1, $promises)->then(function ($values) {
229 |             return $values[0];
230 |         });
231 |     }
232 | 
233 |     /**
234 |      * Returns a promise that is fulfilled when all of the provided promises have
235 |      * been fulfilled or rejected.
236 |      *
237 |      * The returned promise is fulfilled with an array of inspection state arrays.
238 |      *
239 |      * @see inspect for the inspection state array format.
240 |      *
241 |      * @param mixed $promises Promises or values.
242 |      */
243 |     public static function settle($promises): PromiseInterface
244 |     {
245 |         $results = [];
246 | 
247 |         return Each::of(
248 |             $promises,
249 |             function ($value, $idx) use (&$results): void {
250 |                 $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
251 |             },
252 |             function ($reason, $idx) use (&$results): void {
253 |                 $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
254 |             }
255 |         )->then(function () use (&$results) {
256 |             ksort($results);
257 | 
258 |             return $results;
259 |         });
260 |     }
261 | }
262 | 


--------------------------------------------------------------------------------