Full Code of guzzle/promises for AI

2.3 74c1b7186ecd cached
53 files
152.6 KB
39.5k tokens
297 symbols
1 requests
Download .txt
Repository: guzzle/promises
Branch: 2.3
Commit: 74c1b7186ecd
Files: 53
Total size: 152.6 KB

Directory structure:
gitextract_7g2oiihb/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── .editorconfig
│   ├── FUNDING.yml
│   ├── stale.yml
│   └── workflows/
│       ├── checks.yml
│       ├── ci.yml
│       └── static.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── 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
├── tests/
│   ├── AggregateExceptionTest.php
│   ├── CoroutineTest.php
│   ├── CreateTest.php
│   ├── EachPromiseTest.php
│   ├── EachTest.php
│   ├── FulfilledPromiseTest.php
│   ├── IsTest.php
│   ├── NotPromiseInstance.php
│   ├── PromiseTest.php
│   ├── PropertyHelper.php
│   ├── RejectedPromiseTest.php
│   ├── RejectionExceptionTest.php
│   ├── TaskQueueTest.php
│   ├── Thennable.php
│   ├── Thing1.php
│   ├── Thing2.php
│   └── UtilsTest.php
└── vendor-bin/
    ├── php-cs-fixer/
    │   └── composer.json
    └── phpstan/
        └── composer.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: .gitattributes
================================================
.editorconfig           export-ignore
.gitattributes          export-ignore
/.github/               export-ignore
.gitignore              export-ignore
/.php-cs-fixer.dist.php export-ignore
/phpstan-baseline.neon  export-ignore
/phpstan.neon.dist      export-ignore
/phpunit.xml.dist       export-ignore
/psalm-baseline.xml     export-ignore
/psalm.xml              export-ignore
/tests/                 export-ignore
/vendor-bin/            export-ignore
/Makefile               export-ignore


================================================
FILE: .github/.editorconfig
================================================
[*.yml]
indent_size = 2


================================================
FILE: .github/FUNDING.yml
================================================
github: [Nyholm, GrahamCampbell]
tidelift: "packagist/guzzlehttp/promises"


================================================
FILE: .github/stale.yml
================================================
daysUntilStale: 120
daysUntilClose: 14
exemptLabels:
  - lifecycle/keep-open
  - lifecycle/ready-for-merge
# Label to use when marking an issue as stale
staleLabel: lifecycle/stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
  This issue has been automatically marked as stale because it has not had
  recent activity. It will be closed after 2 weeks if no further activity occurs. Thank you
  for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false


================================================
FILE: .github/workflows/checks.yml
================================================
name: Checks

on:
  push:
    branches:
  pull_request:

permissions:
  contents: read

jobs:
  composer-normalize:
    name: Composer Normalize
    runs-on: ubuntu-24.04

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Composer normalize
        uses: docker://ergebnis/composer-normalize-action


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
    branches:
  pull_request:

permissions:
  contents: read

jobs:
  build-lowest-version:
    name: Build lowest version
    runs-on: ubuntu-24.04

    steps:
      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '7.2'
          ini-values: error_reporting=E_ALL
          coverage: 'none'
          extensions: mbstring

      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install dependencies
        run: composer update --no-interaction --prefer-stable --prefer-lowest --no-progress

      - name: Run tests
        run: vendor/bin/phpunit 

  build:
    name: Build
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      max-parallel: 10
      matrix:
        php: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']

    steps:
      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          ini-values: error_reporting=E_ALL
          coverage: 'none'
          extensions: mbstring

      - name: Checkout code
        uses: actions/checkout@v6

      - name: Install dependencies
        run: composer update --no-interaction --no-progress

      - name: Run tests
        run: vendor/bin/phpunit 


================================================
FILE: .github/workflows/static.yml
================================================
name: Static analysis

on:
  push:
    branches:
  pull_request:

permissions:
  contents: read

jobs:
  phpstan:
    name: PHPStan
    runs-on: ubuntu-24.04

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.5'
          coverage: none
          extensions: mbstring

      - name: Download dependencies
        run: composer update --no-interaction --no-progress

      - name: Download PHPStan
        run: composer bin phpstan update --no-interaction --no-progress

      - name: Execute PHPStan
        run: vendor/bin/phpstan analyze --no-progress

  php-cs-fixer:
    name: PHP-CS-Fixer
    runs-on: ubuntu-24.04

    steps:
      - name: Checkout code
        uses: actions/checkout@v6

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '7.4'
          coverage: none
          extensions: mbstring

      - name: Download dependencies
        run: composer update --no-interaction --no-progress

      - name: Download PHP CS Fixer
        run: composer bin php-cs-fixer update --no-interaction --no-progress

      - name: Execute PHP CS Fixer
        run: vendor/bin/php-cs-fixer fix --diff --dry-run


================================================
FILE: .gitignore
================================================
artifacts/
vendor/
composer.lock
phpunit.xml
.php-cs-fixer.php
.php-cs-fixer.cache
.phpunit.result.cache


================================================
FILE: .php-cs-fixer.dist.php
================================================
<?php

$config = (new PhpCsFixer\Config())
    ->setRiskyAllowed(true)
    ->setRules([
        '@PHP71Migration:risky' => true,
        '@PHPUnit75Migration:risky' => true,
        '@PSR12:risky' => true,
        '@Symfony' => true,
        'global_namespace_import' => false,
        'no_superfluous_phpdoc_tags' => [
            'allow_mixed' => true,
        ],
        'phpdoc_annotation_without_dot' => false,
        'phpdoc_summary' => false,
        'phpdoc_to_comment' => false,
        'single_line_throw' => false,
        'yoda_style' => false,
    ])
    ->setFinder(
        PhpCsFixer\Finder::create()
            ->in(__DIR__.'/src')
            ->in(__DIR__.'/tests')
            ->name('*.php')
    )
;

return $config;


================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG


## 2.3.0 - 2025-08-22

### Added

- PHP 8.5 support


## 2.2.0 - 2025-03-27

### Fixed

- Revert "Allow an empty EachPromise to be resolved by running the queue"


## 2.1.0 - 2025-03-27

### Added

- Allow an empty EachPromise to be resolved by running the queue


## 2.0.4 - 2024-10-17

### Fixed

- Once settled, don't allow further rejection of additional promises


## 2.0.3 - 2024-07-18

### Changed

- PHP 8.4 support


## 2.0.2 - 2023-12-03

### Changed

- Replaced `call_user_func*` with native calls


## 2.0.1 - 2023-08-03

### Changed

- PHP 8.3 support


## 2.0.0 - 2023-05-21

### Added

- Added PHP 7 type hints

### Changed

- All previously non-final non-exception classes have been marked as soft-final

### Removed

- Dropped PHP < 7.2 support
- All functions in the `GuzzleHttp\Promise` namespace


## 1.5.3 - 2023-05-21

### Changed

- Removed remaining usage of deprecated functions


## 1.5.2 - 2022-08-07

### Changed

- Officially support PHP 8.2


## 1.5.1 - 2021-10-22

### Fixed

- Revert "Call handler when waiting on fulfilled/rejected Promise"
- Fix pool memory leak when empty array of promises provided


## 1.5.0 - 2021-10-07

### Changed

- Call handler when waiting on fulfilled/rejected Promise
- Officially support PHP 8.1

### Fixed

- Fix manually settle promises generated with `Utils::task`


## 1.4.1 - 2021-02-18

### Fixed

- Fixed `each_limit` skipping promises and failing


## 1.4.0 - 2020-09-30

### Added

- Support for PHP 8
- Optional `$recursive` flag to `all`
- Replaced functions by static methods

### Fixed

- Fix empty `each` processing
- Fix promise handling for Iterators of non-unique keys
- Fixed `method_exists` crashes on PHP 8
- Memory leak on exceptions


## 1.3.1 - 2016-12-20

### Fixed

- `wait()` foreign promise compatibility


## 1.3.0 - 2016-11-18

### Added

- Adds support for custom task queues.

### Fixed

- Fixed coroutine promise memory leak.


## 1.2.0 - 2016-05-18

### Changed

- Update to now catch `\Throwable` on PHP 7+


## 1.1.0 - 2016-03-07

### Changed

- Update EachPromise to prevent recurring on a iterator when advancing, as this
  could trigger fatal generator errors.
- Update Promise to allow recursive waiting without unwrapping exceptions.


## 1.0.3 - 2015-10-15

### Changed

- Update EachPromise to immediately resolve when the underlying promise iterator
  is empty. Previously, such a promise would throw an exception when its `wait`
  function was called.


## 1.0.2 - 2015-05-15

### Changed

- Conditionally require functions.php.


## 1.0.1 - 2015-06-24

### Changed

- Updating EachPromise to call next on the underlying promise iterator as late
  as possible to ensure that generators that generate new requests based on
  callbacks are not iterated until after callbacks are invoked.


## 1.0.0 - 2015-05-12

- Initial release


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com>
Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk>
Copyright (c) 2017 Tobias Schultze <webmaster@tubo-world.de>
Copyright (c) 2020 Tobias Nyholm <tobias.nyholm@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Makefile
================================================
all: clean test

test:
	vendor/bin/phpunit

coverage:
	vendor/bin/phpunit --coverage-html=artifacts/coverage

view-coverage:
	open artifacts/coverage/index.html

clean:
	rm -rf artifacts/*


================================================
FILE: README.md
================================================
# Guzzle Promises

[Promises/A+](https://promisesaplus.com/) implementation that handles promise
chaining and resolution iteratively, allowing for "infinite" promise chaining
while keeping the stack size constant. Read [this blog post](https://blog.domenic.me/youre-missing-the-point-of-promises/)
for a general introduction to promises.

- [Features](#features)
- [Quick start](#quick-start)
- [Synchronous wait](#synchronous-wait)
- [Cancellation](#cancellation)
- [API](#api)
  - [Promise](#promise)
  - [FulfilledPromise](#fulfilledpromise)
  - [RejectedPromise](#rejectedpromise)
- [Promise interop](#promise-interop)
- [Implementation notes](#implementation-notes)


## Features

- [Promises/A+](https://promisesaplus.com/) implementation.
- Promise resolution and chaining is handled iteratively, allowing for
  "infinite" promise chaining.
- Promises have a synchronous `wait` method.
- Promises can be cancelled.
- Works with any object that has a `then` function.
- C# style async/await coroutine promises using
  `GuzzleHttp\Promise\Coroutine::of()`.


## Installation

```shell
composer require guzzlehttp/promises
```


## Version Guidance

| Version | Status              | PHP Version  |
|---------|---------------------|--------------|
| 1.x     | Security fixes only | >=5.5,<8.3   |
| 2.x     | Latest              | >=7.2.5,<8.6 |


## Quick Start

A *promise* represents the eventual result of an asynchronous operation. The
primary way of interacting with a promise is through its `then` method, which
registers callbacks to receive either a promise's eventual value or the reason
why the promise cannot be fulfilled.

### Callbacks

Callbacks are registered with the `then` method by providing an optional 
`$onFulfilled` followed by an optional `$onRejected` function.


```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(
    // $onFulfilled
    function ($value) {
        echo 'The promise was fulfilled.';
    },
    // $onRejected
    function ($reason) {
        echo 'The promise was rejected.';
    }
);
```

*Resolving* a promise means that you either fulfill a promise with a *value* or
reject a promise with a *reason*. Resolving a promise triggers callbacks
registered with the promise's `then` method. These callbacks are triggered
only once and in the order in which they were added.

### Resolving a Promise

Promises are fulfilled using the `resolve($value)` method. Resolving a promise
with any value other than a `GuzzleHttp\Promise\RejectedPromise` will trigger
all of the onFulfilled callbacks (resolving a promise with a rejected promise
will reject the promise and trigger the `$onRejected` callbacks).

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(function ($value) {
        // Return a value and don't break the chain
        return "Hello, " . $value;
    })
    // This then is executed after the first then and receives the value
    // returned from the first then.
    ->then(function ($value) {
        echo $value;
    });

// Resolving the promise triggers the $onFulfilled callbacks and outputs
// "Hello, reader."
$promise->resolve('reader.');
```

### Promise Forwarding

Promises can be chained one after the other. Each then in the chain is a new
promise. The return value of a promise is what's forwarded to the next
promise in the chain. Returning a promise in a `then` callback will cause the
subsequent promises in the chain to only be fulfilled when the returned promise
has been fulfilled. The next promise in the chain will be invoked with the
resolved value of the promise.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$nextPromise = new Promise();

$promise
    ->then(function ($value) use ($nextPromise) {
        echo $value;
        return $nextPromise;
    })
    ->then(function ($value) {
        echo $value;
    });

// Triggers the first callback and outputs "A"
$promise->resolve('A');
// Triggers the second callback and outputs "B"
$nextPromise->resolve('B');
```

### Promise Rejection

When a promise is rejected, the `$onRejected` callbacks are invoked with the
rejection reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    echo $reason;
});

$promise->reject('Error!');
// Outputs "Error!"
```

### Rejection Forwarding

If an exception is thrown in an `$onRejected` callback, subsequent
`$onRejected` callbacks are invoked with the thrown exception as the reason.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    throw new Exception($reason);
})->then(null, function ($reason) {
    assert($reason->getMessage() === 'Error!');
});

$promise->reject('Error!');
```

You can also forward a rejection down the promise chain by returning a
`GuzzleHttp\Promise\RejectedPromise` in either an `$onFulfilled` or
`$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;

$promise = new Promise();
$promise->then(null, function ($reason) {
    return new RejectedPromise($reason);
})->then(null, function ($reason) {
    assert($reason === 'Error!');
});

$promise->reject('Error!');
```

If an exception is not thrown in a `$onRejected` callback and the callback
does not return a rejected promise, downstream `$onFulfilled` callbacks are
invoked using the value returned from the `$onRejected` callback.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise();
$promise
    ->then(null, function ($reason) {
        return "It's ok";
    })
    ->then(function ($value) {
        assert($value === "It's ok");
    });

$promise->reject('Error!');
```


## Synchronous Wait

You can synchronously force promises to complete using a promise's `wait`
method. When creating a promise, you can provide a wait function that is used
to synchronously force a promise to complete. When a wait function is invoked
it is expected to deliver a value to the promise or reject the promise. If the
wait function does not deliver a value, then an exception is thrown. The wait
function provided to a promise constructor is invoked when the `wait` function
of the promise is called.

```php
$promise = new Promise(function () use (&$promise) {
    $promise->resolve('foo');
});

// Calling wait will return the value of the promise.
echo $promise->wait(); // outputs "foo"
```

If an exception is encountered while invoking the wait function of a promise,
the promise is rejected with the exception and the exception is thrown.

```php
$promise = new Promise(function () use (&$promise) {
    throw new Exception('foo');
});

$promise->wait(); // throws the exception.
```

Calling `wait` on a promise that has been fulfilled will not trigger the wait
function. It will simply return the previously resolved value.

```php
$promise = new Promise(function () { die('this is not called!'); });
$promise->resolve('foo');
echo $promise->wait(); // outputs "foo"
```

Calling `wait` on a promise that has been rejected will throw an exception. If
the rejection reason is an instance of `\Exception` the reason is thrown.
Otherwise, a `GuzzleHttp\Promise\RejectionException` is thrown and the reason
can be obtained by calling the `getReason` method of the exception.

```php
$promise = new Promise();
$promise->reject('foo');
$promise->wait();
```

> PHP Fatal error:  Uncaught exception 'GuzzleHttp\Promise\RejectionException' with message 'The promise was rejected with value: foo'

### Unwrapping a Promise

When synchronously waiting on a promise, you are joining the state of the
promise into the current state of execution (i.e., return the value of the
promise if it was fulfilled or throw an exception if it was rejected). This is
called "unwrapping" the promise. Waiting on a promise will by default unwrap
the promise state.

You can force a promise to resolve and *not* unwrap the state of the promise
by passing `false` to the first argument of the `wait` function:

```php
$promise = new Promise();
$promise->reject('foo');
// This will not throw an exception. It simply ensures the promise has
// been resolved.
$promise->wait(false);
```

When unwrapping a promise, the resolved value of the promise will be waited
upon until the unwrapped value is not a promise. This means that if you resolve
promise A with a promise B and unwrap promise A, the value returned by the
wait function will be the value delivered to promise B.

**Note**: when you do not unwrap the promise, no value is returned.


## Cancellation

You can cancel a promise that has not yet been fulfilled using the `cancel()`
method of a promise. When creating a promise you can provide an optional
cancel function that when invoked cancels the action of computing a resolution
of the promise.


## API

### Promise

When creating a promise object, you can provide an optional `$waitFn` and
`$cancelFn`. `$waitFn` is a function that is invoked with no arguments and is
expected to resolve the promise. `$cancelFn` is a function with no arguments
that is expected to cancel the computation of a promise. It is invoked when the
`cancel()` method of a promise is called.

```php
use GuzzleHttp\Promise\Promise;

$promise = new Promise(
    function () use (&$promise) {
        $promise->resolve('waited');
    },
    function () {
        // do something that will cancel the promise computation (e.g., close
        // a socket, cancel a database query, etc...)
    }
);

assert('waited' === $promise->wait());
```

A promise has the following methods:

- `then(callable $onFulfilled, callable $onRejected) : PromiseInterface`
  
  Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler.

- `otherwise(callable $onRejected) : PromiseInterface`
  
  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.

- `wait($unwrap = true) : mixed`

  Synchronously waits on the promise to complete.
  
  `$unwrap` controls whether or not the value of the promise is returned for a
  fulfilled promise or if an exception is thrown if the promise is rejected.
  This is set to `true` by default.

- `cancel()`

  Attempts to cancel the promise if possible. The promise being cancelled and
  the parent most ancestor that has not yet been resolved will also be
  cancelled. Any promises waiting on the cancelled promise to resolve will also
  be cancelled.

- `getState() : string`

  Returns the state of the promise. One of `pending`, `fulfilled`, or
  `rejected`.

- `resolve($value)`

  Fulfills the promise with the given `$value`.

- `reject($reason)`

  Rejects the promise with the given `$reason`.


### FulfilledPromise

A fulfilled promise can be created to represent a promise that has been
fulfilled.

```php
use GuzzleHttp\Promise\FulfilledPromise;

$promise = new FulfilledPromise('value');

// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
    echo $value;
});
```


### RejectedPromise

A rejected promise can be created to represent a promise that has been
rejected.

```php
use GuzzleHttp\Promise\RejectedPromise;

$promise = new RejectedPromise('Error');

// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
    echo $reason;
});
```


## Promise Interoperability

This library works with foreign promises that have a `then` method. This means
you can use Guzzle promises with [React promises](https://github.com/reactphp/promise)
for example. When a foreign promise is returned inside of a then method
callback, promise resolution will occur recursively.

```php
// Create a React promise
$deferred = new React\Promise\Deferred();
$reactPromise = $deferred->promise();

// Create a Guzzle promise that is fulfilled with a React promise.
$guzzlePromise = new GuzzleHttp\Promise\Promise();
$guzzlePromise->then(function ($value) use ($reactPromise) {
    // Do something something with the value...
    // Return the React promise
    return $reactPromise;
});
```

Please note that wait and cancel chaining is no longer possible when forwarding
a foreign promise. You will need to wrap a third-party promise with a Guzzle
promise in order to utilize wait and cancel functions with foreign promises.


### Event Loop Integration

In order to keep the stack size constant, Guzzle promises are resolved
asynchronously using a task queue. When waiting on promises synchronously, the
task queue will be automatically run to ensure that the blocking promise and
any forwarded promises are resolved. When using promises asynchronously in an
event loop, you will need to run the task queue on each tick of the loop. If
you do not run the task queue, then promises will not be resolved.

You can run the task queue using the `run()` method of the global task queue
instance.

```php
// Get the global task queue
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();
```

For example, you could use Guzzle promises with React using a periodic timer:

```php
$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0, [$queue, 'run']);
```


## Implementation Notes

### Promise Resolution and Chaining is Handled Iteratively

By shuffling pending handlers from one owner to another, promises are
resolved iteratively, allowing for "infinite" then chaining.

```php
<?php
require 'vendor/autoload.php';

use GuzzleHttp\Promise\Promise;

$parent = new Promise();
$p = $parent;

for ($i = 0; $i < 1000; $i++) {
    $p = $p->then(function ($v) {
        // The stack size remains constant (a good thing)
        echo xdebug_get_stack_depth() . ', ';
        return $v + 1;
    });
}

$parent->resolve(0);
var_dump($p->wait()); // int(1000)

```

When a promise is fulfilled or rejected with a non-promise value, the promise
then takes ownership of the handlers of each child promise and delivers values
down the chain without using recursion.

When a promise is resolved with another promise, the original promise transfers
all of its pending handlers to the new promise. When the new promise is
eventually resolved, all of the pending handlers are delivered the forwarded
value.

### A Promise is the Deferred

Some promise libraries implement promises using a deferred object to represent
a computation and a promise object to represent the delivery of the result of
the computation. This is a nice separation of computation and delivery because
consumers of the promise cannot modify the value that will be eventually
delivered.

One side effect of being able to implement promise resolution and chaining
iteratively is that you need to be able for one promise to reach into the state
of another promise to shuffle around ownership of handlers. In order to achieve
this without making the handlers of a promise publicly mutable, a promise is
also the deferred value, allowing promises of the same parent class to reach
into and modify the private properties of promises of the same type. While this
does allow consumers of the value to modify the resolution or rejection of the
deferred, it is a small price to pay for keeping the stack size constant.

```php
$promise = new Promise();
$promise->then(function ($value) { echo $value; });
// The promise is the deferred value, so you can deliver a value to it.
$promise->resolve('foo');
// prints "foo"
```


## Upgrading from Function API

A static API was first introduced in 1.4.0, in order to mitigate problems with
functions conflicting between global and local copies of the package. The
function API was removed in 2.0.0. A migration table has been provided here for
your convenience:

| Original Function | Replacement Method |
|----------------|----------------|
| `queue` | `Utils::queue` |
| `task` | `Utils::task` |
| `promise_for` | `Create::promiseFor` |
| `rejection_for` | `Create::rejectionFor` |
| `exception_for` | `Create::exceptionFor` |
| `iter_for` | `Create::iterFor` |
| `inspect` | `Utils::inspect` |
| `inspect_all` | `Utils::inspectAll` |
| `unwrap` | `Utils::unwrap` |
| `all` | `Utils::all` |
| `some` | `Utils::some` |
| `any` | `Utils::any` |
| `settle` | `Utils::settle` |
| `each` | `Each::of` |
| `each_limit` | `Each::ofLimit` |
| `each_limit_all` | `Each::ofLimitAll` |
| `!is_fulfilled` | `Is::pending` |
| `is_fulfilled` | `Is::fulfilled` |
| `is_rejected` | `Is::rejected` |
| `is_settled` | `Is::settled` |
| `coroutine` | `Coroutine::of` |


## Security

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.


## License

Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.


## For Enterprise

Available as part of the Tidelift Subscription

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)


================================================
FILE: composer.json
================================================
{
    "name": "guzzlehttp/promises",
    "description": "Guzzle promises library",
    "keywords": ["promise"],
    "license": "MIT",
    "authors": [
        {
            "name": "Graham Campbell",
            "email": "hello@gjcampbell.co.uk",
            "homepage": "https://github.com/GrahamCampbell"
        },
        {
            "name": "Michael Dowling",
            "email": "mtdowling@gmail.com",
            "homepage": "https://github.com/mtdowling"
        },
        {
            "name": "Tobias Nyholm",
            "email": "tobias.nyholm@gmail.com",
            "homepage": "https://github.com/Nyholm"
        },
        {
            "name": "Tobias Schultze",
            "email": "webmaster@tubo-world.de",
            "homepage": "https://github.com/Tobion"
        }
    ],
    "require": {
        "php": "^7.2.5 || ^8.0"
    },
    "require-dev": {
        "bamarni/composer-bin-plugin": "^1.8.2",
        "phpunit/phpunit": "^8.5.44 || ^9.6.25"
    },
    "autoload": {
        "psr-4": {
            "GuzzleHttp\\Promise\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "GuzzleHttp\\Promise\\Tests\\": "tests/"
        }
    },
    "extra": {
        "bamarni-bin": {
            "bin-links": true,
            "forward-command": false
        }
    },
    "config": {
        "allow-plugins": {
            "bamarni/composer-bin-plugin": true
        },
        "preferred-install": "dist",
        "sort-packages": true
    }
}


================================================
FILE: phpstan-baseline.neon
================================================
parameters:
	ignoreErrors:
		-
			message: '#^Dead catch \- GuzzleHttp\\Promise\\RejectionException is never thrown in the try block\.$#'
			identifier: catch.neverThrown
			count: 1
			path: src/Utils.php



================================================
FILE: phpstan.neon.dist
================================================
includes:
    - phpstan-baseline.neon

parameters:
    level: 5
    paths:
        - src


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    colors="true"
    beStrictAboutOutputDuringTests="true"
    beStrictAboutTestsThatDoNotTestAnything="true"
    bootstrap="vendor/autoload.php"
>
    <testsuites>
        <testsuite name="GuzzleHttp Promise Test Suite">
            <directory>tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory>src/</directory>
            <exclude>
                <directory suffix="Interface.php">src/</directory>
            </exclude>
        </whitelist>
    </filter>
</phpunit>


================================================
FILE: src/AggregateException.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Exception thrown when too many errors occur in the some() or any() methods.
 */
class AggregateException extends RejectionException
{
    public function __construct(string $msg, array $reasons)
    {
        parent::__construct(
            $reasons,
            sprintf('%s; %d rejected promises', $msg, count($reasons))
        );
    }
}


================================================
FILE: src/CancellationException.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Exception that is set as the reason for a promise that has been cancelled.
 */
class CancellationException extends RejectionException
{
}


================================================
FILE: src/Coroutine.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

use Generator;
use Throwable;

/**
 * Creates a promise that is resolved using a generator that yields values or
 * promises (somewhat similar to C#'s async keyword).
 *
 * When called, the Coroutine::of method will start an instance of the generator
 * and returns a promise that is fulfilled with its final yielded value.
 *
 * Control is returned back to the generator when the yielded promise settles.
 * This can lead to less verbose code when doing lots of sequential async calls
 * with minimal processing in between.
 *
 *     use GuzzleHttp\Promise;
 *
 *     function createPromise($value) {
 *         return new Promise\FulfilledPromise($value);
 *     }
 *
 *     $promise = Promise\Coroutine::of(function () {
 *         $value = (yield createPromise('a'));
 *         try {
 *             $value = (yield createPromise($value . 'b'));
 *         } catch (\Throwable $e) {
 *             // The promise was rejected.
 *         }
 *         yield $value . 'c';
 *     });
 *
 *     // Outputs "abc"
 *     $promise->then(function ($v) { echo $v; });
 *
 * @param callable $generatorFn Generator function to wrap into a promise.
 *
 * @return Promise
 *
 * @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
 */
final class Coroutine implements PromiseInterface
{
    /**
     * @var PromiseInterface|null
     */
    private $currentPromise;

    /**
     * @var Generator
     */
    private $generator;

    /**
     * @var Promise
     */
    private $result;

    public function __construct(callable $generatorFn)
    {
        $this->generator = $generatorFn();
        $this->result = new Promise(function (): void {
            while (isset($this->currentPromise)) {
                $this->currentPromise->wait();
            }
        });
        try {
            $this->nextCoroutine($this->generator->current());
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }

    /**
     * Create a new coroutine.
     */
    public static function of(callable $generatorFn): self
    {
        return new self($generatorFn);
    }

    public function then(
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        return $this->result->then($onFulfilled, $onRejected);
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->result->otherwise($onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        return $this->result->wait($unwrap);
    }

    public function getState(): string
    {
        return $this->result->getState();
    }

    public function resolve($value): void
    {
        $this->result->resolve($value);
    }

    public function reject($reason): void
    {
        $this->result->reject($reason);
    }

    public function cancel(): void
    {
        $this->currentPromise->cancel();
        $this->result->cancel();
    }

    private function nextCoroutine($yielded): void
    {
        $this->currentPromise = Create::promiseFor($yielded)
            ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
    }

    /**
     * @internal
     */
    public function _handleSuccess($value): void
    {
        unset($this->currentPromise);
        try {
            $next = $this->generator->send($value);
            if ($this->generator->valid()) {
                $this->nextCoroutine($next);
            } else {
                $this->result->resolve($value);
            }
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }

    /**
     * @internal
     */
    public function _handleFailure($reason): void
    {
        unset($this->currentPromise);
        try {
            $nextYield = $this->generator->throw(Create::exceptionFor($reason));
            // The throw was caught, so keep iterating on the coroutine
            $this->nextCoroutine($nextYield);
        } catch (Throwable $throwable) {
            $this->result->reject($throwable);
        }
    }
}


================================================
FILE: src/Create.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Create
{
    /**
     * Creates a promise for a value if the value is not a promise.
     *
     * @param mixed $value Promise or value.
     */
    public static function promiseFor($value): PromiseInterface
    {
        if ($value instanceof PromiseInterface) {
            return $value;
        }

        // Return a Guzzle promise that shadows the given promise.
        if (is_object($value) && method_exists($value, 'then')) {
            $wfn = method_exists($value, 'wait') ? [$value, 'wait'] : null;
            $cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
            $promise = new Promise($wfn, $cfn);
            $value->then([$promise, 'resolve'], [$promise, 'reject']);

            return $promise;
        }

        return new FulfilledPromise($value);
    }

    /**
     * Creates a rejected promise for a reason if the reason is not a promise.
     * If the provided reason is a promise, then it is returned as-is.
     *
     * @param mixed $reason Promise or reason.
     */
    public static function rejectionFor($reason): PromiseInterface
    {
        if ($reason instanceof PromiseInterface) {
            return $reason;
        }

        return new RejectedPromise($reason);
    }

    /**
     * Create an exception for a rejected promise value.
     *
     * @param mixed $reason
     */
    public static function exceptionFor($reason): \Throwable
    {
        if ($reason instanceof \Throwable) {
            return $reason;
        }

        return new RejectionException($reason);
    }

    /**
     * Returns an iterator for the given value.
     *
     * @param mixed $value
     */
    public static function iterFor($value): \Iterator
    {
        if ($value instanceof \Iterator) {
            return $value;
        }

        if (is_array($value)) {
            return new \ArrayIterator($value);
        }

        return new \ArrayIterator([$value]);
    }
}


================================================
FILE: src/Each.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Each
{
    /**
     * Given an iterator that yields promises or values, returns a promise that
     * is fulfilled with a null value when the iterator has been consumed or
     * the aggregate promise has been fulfilled or rejected.
     *
     * $onFulfilled is a function that accepts the fulfilled value, iterator
     * index, and the aggregate promise. The callback can invoke any necessary
     * side effects and choose to resolve or reject the aggregate if needed.
     *
     * $onRejected is a function that accepts the rejection reason, iterator
     * index, and the aggregate promise. The callback can invoke any necessary
     * side effects and choose to resolve or reject the aggregate if needed.
     *
     * @param mixed $iterable Iterator or array to iterate over.
     */
    public static function of(
        $iterable,
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        return (new EachPromise($iterable, [
            'fulfilled' => $onFulfilled,
            'rejected' => $onRejected,
        ]))->promise();
    }

    /**
     * Like of, but only allows a certain number of outstanding promises at any
     * given time.
     *
     * $concurrency may be an integer or a function that accepts the number of
     * pending promises and returns a numeric concurrency limit value to allow
     * for dynamic a concurrency size.
     *
     * @param mixed        $iterable
     * @param int|callable $concurrency
     */
    public static function ofLimit(
        $iterable,
        $concurrency,
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        return (new EachPromise($iterable, [
            'fulfilled' => $onFulfilled,
            'rejected' => $onRejected,
            'concurrency' => $concurrency,
        ]))->promise();
    }

    /**
     * Like limit, but ensures that no promise in the given $iterable argument
     * is rejected. If any promise is rejected, then the aggregate promise is
     * rejected with the encountered rejection.
     *
     * @param mixed        $iterable
     * @param int|callable $concurrency
     */
    public static function ofLimitAll(
        $iterable,
        $concurrency,
        ?callable $onFulfilled = null
    ): PromiseInterface {
        return self::ofLimit(
            $iterable,
            $concurrency,
            $onFulfilled,
            function ($reason, $idx, PromiseInterface $aggregate): void {
                $aggregate->reject($reason);
            }
        );
    }
}


================================================
FILE: src/EachPromise.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Represents a promise that iterates over many promises and invokes
 * side-effect functions in the process.
 *
 * @final
 */
class EachPromise implements PromisorInterface
{
    private $pending = [];

    private $nextPendingIndex = 0;

    /** @var \Iterator|null */
    private $iterable;

    /** @var callable|int|null */
    private $concurrency;

    /** @var callable|null */
    private $onFulfilled;

    /** @var callable|null */
    private $onRejected;

    /** @var Promise|null */
    private $aggregate;

    /** @var bool|null */
    private $mutex;

    /**
     * Configuration hash can include the following key value pairs:
     *
     * - fulfilled: (callable) Invoked when a promise fulfills. The function
     *   is invoked with three arguments: the fulfillment value, the index
     *   position from the iterable list of the promise, and the aggregate
     *   promise that manages all of the promises. The aggregate promise may
     *   be resolved from within the callback to short-circuit the promise.
     * - rejected: (callable) Invoked when a promise is rejected. The
     *   function is invoked with three arguments: the rejection reason, the
     *   index position from the iterable list of the promise, and the
     *   aggregate promise that manages all of the promises. The aggregate
     *   promise may be resolved from within the callback to short-circuit
     *   the promise.
     * - concurrency: (integer) Pass this configuration option to limit the
     *   allowed number of outstanding concurrently executing promises,
     *   creating a capped pool of promises. There is no limit by default.
     *
     * @param mixed $iterable Promises or values to iterate.
     * @param array $config   Configuration options
     */
    public function __construct($iterable, array $config = [])
    {
        $this->iterable = Create::iterFor($iterable);

        if (isset($config['concurrency'])) {
            $this->concurrency = $config['concurrency'];
        }

        if (isset($config['fulfilled'])) {
            $this->onFulfilled = $config['fulfilled'];
        }

        if (isset($config['rejected'])) {
            $this->onRejected = $config['rejected'];
        }
    }

    /** @psalm-suppress InvalidNullableReturnType */
    public function promise(): PromiseInterface
    {
        if ($this->aggregate) {
            return $this->aggregate;
        }

        try {
            $this->createPromise();
            /** @psalm-assert Promise $this->aggregate */
            $this->iterable->rewind();
            $this->refillPending();
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
        }

        /**
         * @psalm-suppress NullableReturnStatement
         */
        return $this->aggregate;
    }

    private function createPromise(): void
    {
        $this->mutex = false;
        $this->aggregate = new Promise(function (): void {
            if ($this->checkIfFinished()) {
                return;
            }
            reset($this->pending);
            // Consume a potentially fluctuating list of promises while
            // ensuring that indexes are maintained (precluding array_shift).
            while ($promise = current($this->pending)) {
                next($this->pending);
                $promise->wait();
                if (Is::settled($this->aggregate)) {
                    return;
                }
            }
        });

        // Clear the references when the promise is resolved.
        $clearFn = function (): void {
            $this->iterable = $this->concurrency = $this->pending = null;
            $this->onFulfilled = $this->onRejected = null;
            $this->nextPendingIndex = 0;
        };

        $this->aggregate->then($clearFn, $clearFn);
    }

    private function refillPending(): void
    {
        if (!$this->concurrency) {
            // Add all pending promises.
            while ($this->addPending() && $this->advanceIterator()) {
            }

            return;
        }

        // Add only up to N pending promises.
        $concurrency = is_callable($this->concurrency)
            ? ($this->concurrency)(count($this->pending))
            : $this->concurrency;
        $concurrency = max($concurrency - count($this->pending), 0);
        // Concurrency may be set to 0 to disallow new promises.
        if (!$concurrency) {
            return;
        }
        // Add the first pending promise.
        $this->addPending();
        // Note this is special handling for concurrency=1 so that we do
        // not advance the iterator after adding the first promise. This
        // helps work around issues with generators that might not have the
        // next value to yield until promise callbacks are called.
        while (--$concurrency
            && $this->advanceIterator()
            && $this->addPending()) {
        }
    }

    private function addPending(): bool
    {
        if (!$this->iterable || !$this->iterable->valid()) {
            return false;
        }

        $promise = Create::promiseFor($this->iterable->current());
        $key = $this->iterable->key();

        // Iterable keys may not be unique, so we use a counter to
        // guarantee uniqueness
        $idx = $this->nextPendingIndex++;

        $this->pending[$idx] = $promise->then(
            function ($value) use ($idx, $key): void {
                if ($this->onFulfilled) {
                    ($this->onFulfilled)(
                        $value,
                        $key,
                        $this->aggregate
                    );
                }
                $this->step($idx);
            },
            function ($reason) use ($idx, $key): void {
                if ($this->onRejected) {
                    ($this->onRejected)(
                        $reason,
                        $key,
                        $this->aggregate
                    );
                }
                $this->step($idx);
            }
        );

        return true;
    }

    private function advanceIterator(): bool
    {
        // Place a lock on the iterator so that we ensure to not recurse,
        // preventing fatal generator errors.
        if ($this->mutex) {
            return false;
        }

        $this->mutex = true;

        try {
            $this->iterable->next();
            $this->mutex = false;

            return true;
        } catch (\Throwable $e) {
            $this->aggregate->reject($e);
            $this->mutex = false;

            return false;
        }
    }

    private function step(int $idx): void
    {
        // If the promise was already resolved, then ignore this step.
        if (Is::settled($this->aggregate)) {
            return;
        }

        unset($this->pending[$idx]);

        // Only refill pending promises if we are not locked, preventing the
        // EachPromise to recursively invoke the provided iterator, which
        // cause a fatal error: "Cannot resume an already running generator"
        if ($this->advanceIterator() && !$this->checkIfFinished()) {
            // Add more pending promises if possible.
            $this->refillPending();
        }
    }

    private function checkIfFinished(): bool
    {
        if (!$this->pending && !$this->iterable->valid()) {
            // Resolve the promise if there's nothing left to do.
            $this->aggregate->resolve(null);

            return true;
        }

        return false;
    }
}


================================================
FILE: src/FulfilledPromise.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise that has been fulfilled.
 *
 * Thenning off of this promise will invoke the onFulfilled callback
 * immediately and ignore other callbacks.
 *
 * @final
 */
class FulfilledPromise implements PromiseInterface
{
    private $value;

    /**
     * @param mixed $value
     */
    public function __construct($value)
    {
        if (is_object($value) && method_exists($value, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a FulfilledPromise with a promise.'
            );
        }

        $this->value = $value;
    }

    public function then(
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        // Return itself if there is no onFulfilled function.
        if (!$onFulfilled) {
            return $this;
        }

        $queue = Utils::queue();
        $p = new Promise([$queue, 'run']);
        $value = $this->value;
        $queue->add(static function () use ($p, $value, $onFulfilled): void {
            if (Is::pending($p)) {
                try {
                    $p->resolve($onFulfilled($value));
                } catch (\Throwable $e) {
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        return $unwrap ? $this->value : null;
    }

    public function getState(): string
    {
        return self::FULFILLED;
    }

    public function resolve($value): void
    {
        if ($value !== $this->value) {
            throw new \LogicException('Cannot resolve a fulfilled promise');
        }
    }

    public function reject($reason): void
    {
        throw new \LogicException('Cannot reject a fulfilled promise');
    }

    public function cancel(): void
    {
        // pass
    }
}


================================================
FILE: src/Is.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Is
{
    /**
     * Returns true if a promise is pending.
     */
    public static function pending(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::PENDING;
    }

    /**
     * Returns true if a promise is fulfilled or rejected.
     */
    public static function settled(PromiseInterface $promise): bool
    {
        return $promise->getState() !== PromiseInterface::PENDING;
    }

    /**
     * Returns true if a promise is fulfilled.
     */
    public static function fulfilled(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::FULFILLED;
    }

    /**
     * Returns true if a promise is rejected.
     */
    public static function rejected(PromiseInterface $promise): bool
    {
        return $promise->getState() === PromiseInterface::REJECTED;
    }
}


================================================
FILE: src/Promise.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Promises/A+ implementation that avoids recursion when possible.
 *
 * @see https://promisesaplus.com/
 *
 * @final
 */
class Promise implements PromiseInterface
{
    private $state = self::PENDING;
    private $result;
    private $cancelFn;
    private $waitFn;
    private $waitList;
    private $handlers = [];

    /**
     * @param callable $waitFn   Fn that when invoked resolves the promise.
     * @param callable $cancelFn Fn that when invoked cancels the promise.
     */
    public function __construct(
        ?callable $waitFn = null,
        ?callable $cancelFn = null
    ) {
        $this->waitFn = $waitFn;
        $this->cancelFn = $cancelFn;
    }

    public function then(
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        if ($this->state === self::PENDING) {
            $p = new Promise(null, [$this, 'cancel']);
            $this->handlers[] = [$p, $onFulfilled, $onRejected];
            $p->waitList = $this->waitList;
            $p->waitList[] = $this;

            return $p;
        }

        // Return a fulfilled promise and immediately invoke any callbacks.
        if ($this->state === self::FULFILLED) {
            $promise = Create::promiseFor($this->result);

            return $onFulfilled ? $promise->then($onFulfilled) : $promise;
        }

        // It's either cancelled or rejected, so return a rejected promise
        // and immediately invoke any callbacks.
        $rejection = Create::rejectionFor($this->result);

        return $onRejected ? $rejection->then(null, $onRejected) : $rejection;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        $this->waitIfPending();

        if ($this->result instanceof PromiseInterface) {
            return $this->result->wait($unwrap);
        }
        if ($unwrap) {
            if ($this->state === self::FULFILLED) {
                return $this->result;
            }
            // It's rejected so "unwrap" and throw an exception.
            throw Create::exceptionFor($this->result);
        }
    }

    public function getState(): string
    {
        return $this->state;
    }

    public function cancel(): void
    {
        if ($this->state !== self::PENDING) {
            return;
        }

        $this->waitFn = $this->waitList = null;

        if ($this->cancelFn) {
            $fn = $this->cancelFn;
            $this->cancelFn = null;
            try {
                $fn();
            } catch (\Throwable $e) {
                $this->reject($e);
            }
        }

        // Reject the promise only if it wasn't rejected in a then callback.
        /** @psalm-suppress RedundantCondition */
        if ($this->state === self::PENDING) {
            $this->reject(new CancellationException('Promise has been cancelled'));
        }
    }

    public function resolve($value): void
    {
        $this->settle(self::FULFILLED, $value);
    }

    public function reject($reason): void
    {
        $this->settle(self::REJECTED, $reason);
    }

    private function settle(string $state, $value): void
    {
        if ($this->state !== self::PENDING) {
            // Ignore calls with the same resolution.
            if ($state === $this->state && $value === $this->result) {
                return;
            }
            throw $this->state === $state
                ? new \LogicException("The promise is already {$state}.")
                : new \LogicException("Cannot change a {$this->state} promise to {$state}");
        }

        if ($value === $this) {
            throw new \LogicException('Cannot fulfill or reject a promise with itself');
        }

        // Clear out the state of the promise but stash the handlers.
        $this->state = $state;
        $this->result = $value;
        $handlers = $this->handlers;
        $this->handlers = null;
        $this->waitList = $this->waitFn = null;
        $this->cancelFn = null;

        if (!$handlers) {
            return;
        }

        // If the value was not a settled promise or a thenable, then resolve
        // it in the task queue using the correct ID.
        if (!is_object($value) || !method_exists($value, 'then')) {
            $id = $state === self::FULFILLED ? 1 : 2;
            // It's a success, so resolve the handlers in the queue.
            Utils::queue()->add(static function () use ($id, $value, $handlers): void {
                foreach ($handlers as $handler) {
                    self::callHandler($id, $value, $handler);
                }
            });
        } elseif ($value instanceof Promise && Is::pending($value)) {
            // We can just merge our handlers onto the next promise.
            $value->handlers = array_merge($value->handlers, $handlers);
        } else {
            // Resolve the handlers when the forwarded promise is resolved.
            $value->then(
                static function ($value) use ($handlers): void {
                    foreach ($handlers as $handler) {
                        self::callHandler(1, $value, $handler);
                    }
                },
                static function ($reason) use ($handlers): void {
                    foreach ($handlers as $handler) {
                        self::callHandler(2, $reason, $handler);
                    }
                }
            );
        }
    }

    /**
     * Call a stack of handlers using a specific callback index and value.
     *
     * @param int   $index   1 (resolve) or 2 (reject).
     * @param mixed $value   Value to pass to the callback.
     * @param array $handler Array of handler data (promise and callbacks).
     */
    private static function callHandler(int $index, $value, array $handler): void
    {
        /** @var PromiseInterface $promise */
        $promise = $handler[0];

        // The promise may have been cancelled or resolved before placing
        // this thunk in the queue.
        if (Is::settled($promise)) {
            return;
        }

        try {
            if (isset($handler[$index])) {
                /*
                 * If $f throws an exception, then $handler will be in the exception
                 * stack trace. Since $handler contains a reference to the callable
                 * itself we get a circular reference. We clear the $handler
                 * here to avoid that memory leak.
                 */
                $f = $handler[$index];
                unset($handler);
                $promise->resolve($f($value));
            } elseif ($index === 1) {
                // Forward resolution values as-is.
                $promise->resolve($value);
            } else {
                // Forward rejections down the chain.
                $promise->reject($value);
            }
        } catch (\Throwable $reason) {
            $promise->reject($reason);
        }
    }

    private function waitIfPending(): void
    {
        if ($this->state !== self::PENDING) {
            return;
        } elseif ($this->waitFn) {
            $this->invokeWaitFn();
        } elseif ($this->waitList) {
            $this->invokeWaitList();
        } else {
            // If there's no wait function, then reject the promise.
            $this->reject('Cannot wait on a promise that has '
                .'no internal wait function. You must provide a wait '
                .'function when constructing the promise to be able to '
                .'wait on a promise.');
        }

        Utils::queue()->run();

        /** @psalm-suppress RedundantCondition */
        if ($this->state === self::PENDING) {
            $this->reject('Invoking the wait callback did not resolve the promise');
        }
    }

    private function invokeWaitFn(): void
    {
        try {
            $wfn = $this->waitFn;
            $this->waitFn = null;
            $wfn(true);
        } catch (\Throwable $reason) {
            if ($this->state === self::PENDING) {
                // The promise has not been resolved yet, so reject the promise
                // with the exception.
                $this->reject($reason);
            } else {
                // The promise was already resolved, so there's a problem in
                // the application.
                throw $reason;
            }
        }
    }

    private function invokeWaitList(): void
    {
        $waitList = $this->waitList;
        $this->waitList = null;

        foreach ($waitList as $result) {
            do {
                $result->waitIfPending();
                $result = $result->result;
            } while ($result instanceof Promise);

            if ($result instanceof PromiseInterface) {
                $result->wait(false);
            }
        }
    }
}


================================================
FILE: src/PromiseInterface.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise represents the eventual result of an asynchronous operation.
 *
 * The primary way of interacting with a promise is through its then method,
 * which registers callbacks to receive either a promise’s eventual value or
 * the reason why the promise cannot be fulfilled.
 *
 * @see https://promisesaplus.com/
 */
interface PromiseInterface
{
    public const PENDING = 'pending';
    public const FULFILLED = 'fulfilled';
    public const REJECTED = 'rejected';

    /**
     * Appends fulfillment and rejection handlers to the promise, and returns
     * a new promise resolving to the return value of the called handler.
     *
     * @param callable $onFulfilled Invoked when the promise fulfills.
     * @param callable $onRejected  Invoked when the promise is rejected.
     */
    public function then(
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface;

    /**
     * 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.
     *
     * @param callable $onRejected Invoked when the promise is rejected.
     */
    public function otherwise(callable $onRejected): PromiseInterface;

    /**
     * Get the state of the promise ("pending", "rejected", or "fulfilled").
     *
     * The three states can be checked against the constants defined on
     * PromiseInterface: PENDING, FULFILLED, and REJECTED.
     */
    public function getState(): string;

    /**
     * Resolve the promise with the given value.
     *
     * @param mixed $value
     *
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function resolve($value): void;

    /**
     * Reject the promise with the given reason.
     *
     * @param mixed $reason
     *
     * @throws \RuntimeException if the promise is already resolved.
     */
    public function reject($reason): void;

    /**
     * Cancels the promise if possible.
     *
     * @see https://github.com/promises-aplus/cancellation-spec/issues/7
     */
    public function cancel(): void;

    /**
     * Waits until the promise completes if possible.
     *
     * Pass $unwrap as true to unwrap the result of the promise, either
     * returning the resolved value or throwing the rejected exception.
     *
     * If the promise cannot be waited on, then the promise will be rejected.
     *
     * @return mixed
     *
     * @throws \LogicException if the promise has no wait function or if the
     *                         promise does not settle after waiting.
     */
    public function wait(bool $unwrap = true);
}


================================================
FILE: src/PromisorInterface.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * Interface used with classes that return a promise.
 */
interface PromisorInterface
{
    /**
     * Returns a promise.
     */
    public function promise(): PromiseInterface;
}


================================================
FILE: src/RejectedPromise.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A promise that has been rejected.
 *
 * Thenning off of this promise will invoke the onRejected callback
 * immediately and ignore other callbacks.
 *
 * @final
 */
class RejectedPromise implements PromiseInterface
{
    private $reason;

    /**
     * @param mixed $reason
     */
    public function __construct($reason)
    {
        if (is_object($reason) && method_exists($reason, 'then')) {
            throw new \InvalidArgumentException(
                'You cannot create a RejectedPromise with a promise.'
            );
        }

        $this->reason = $reason;
    }

    public function then(
        ?callable $onFulfilled = null,
        ?callable $onRejected = null
    ): PromiseInterface {
        // If there's no onRejected callback then just return self.
        if (!$onRejected) {
            return $this;
        }

        $queue = Utils::queue();
        $reason = $this->reason;
        $p = new Promise([$queue, 'run']);
        $queue->add(static function () use ($p, $reason, $onRejected): void {
            if (Is::pending($p)) {
                try {
                    // Return a resolved promise if onRejected does not throw.
                    $p->resolve($onRejected($reason));
                } catch (\Throwable $e) {
                    // onRejected threw, so return a rejected promise.
                    $p->reject($e);
                }
            }
        });

        return $p;
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then(null, $onRejected);
    }

    public function wait(bool $unwrap = true)
    {
        if ($unwrap) {
            throw Create::exceptionFor($this->reason);
        }

        return null;
    }

    public function getState(): string
    {
        return self::REJECTED;
    }

    public function resolve($value): void
    {
        throw new \LogicException('Cannot resolve a rejected promise');
    }

    public function reject($reason): void
    {
        if ($reason !== $this->reason) {
            throw new \LogicException('Cannot reject a rejected promise');
        }
    }

    public function cancel(): void
    {
        // pass
    }
}


================================================
FILE: src/RejectionException.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A special exception that is thrown when waiting on a rejected promise.
 *
 * The reason value is available via the getReason() method.
 */
class RejectionException extends \RuntimeException
{
    /** @var mixed Rejection reason. */
    private $reason;

    /**
     * @param mixed       $reason      Rejection reason.
     * @param string|null $description Optional description.
     */
    public function __construct($reason, ?string $description = null)
    {
        $this->reason = $reason;

        $message = 'The promise was rejected';

        if ($description) {
            $message .= ' with reason: '.$description;
        } elseif (is_string($reason)
            || (is_object($reason) && method_exists($reason, '__toString'))
        ) {
            $message .= ' with reason: '.$this->reason;
        } elseif ($reason instanceof \JsonSerializable) {
            $message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT);
        }

        parent::__construct($message);
    }

    /**
     * Returns the rejection reason.
     *
     * @return mixed
     */
    public function getReason()
    {
        return $this->reason;
    }
}


================================================
FILE: src/TaskQueue.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

/**
 * A task queue that executes tasks in a FIFO order.
 *
 * This task queue class is used to settle promises asynchronously and
 * maintains a constant stack size. You can use the task queue asynchronously
 * by calling the `run()` function of the global task queue in an event loop.
 *
 *     GuzzleHttp\Promise\Utils::queue()->run();
 *
 * @final
 */
class TaskQueue implements TaskQueueInterface
{
    private $enableShutdown = true;
    private $queue = [];

    public function __construct(bool $withShutdown = true)
    {
        if ($withShutdown) {
            register_shutdown_function(function (): void {
                if ($this->enableShutdown) {
                    // Only run the tasks if an E_ERROR didn't occur.
                    $err = error_get_last();
                    if (!$err || ($err['type'] ^ E_ERROR)) {
                        $this->run();
                    }
                }
            });
        }
    }

    public function isEmpty(): bool
    {
        return !$this->queue;
    }

    public function add(callable $task): void
    {
        $this->queue[] = $task;
    }

    public function run(): void
    {
        while ($task = array_shift($this->queue)) {
            /** @var callable $task */
            $task();
        }
    }

    /**
     * The task queue will be run and exhausted by default when the process
     * exits IFF the exit is not the result of a PHP E_ERROR error.
     *
     * You can disable running the automatic shutdown of the queue by calling
     * this function. If you disable the task queue shutdown process, then you
     * MUST either run the task queue (as a result of running your event loop
     * or manually using the run() method) or wait on each outstanding promise.
     *
     * Note: This shutdown will occur before any destructors are triggered.
     */
    public function disableShutdown(): void
    {
        $this->enableShutdown = false;
    }
}


================================================
FILE: src/TaskQueueInterface.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

interface TaskQueueInterface
{
    /**
     * Returns true if the queue is empty.
     */
    public function isEmpty(): bool;

    /**
     * Adds a task to the queue that will be executed the next time run is
     * called.
     */
    public function add(callable $task): void;

    /**
     * Execute all of the pending task in the queue.
     */
    public function run(): void;
}


================================================
FILE: src/Utils.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise;

final class Utils
{
    /**
     * Get the global task queue used for promise resolution.
     *
     * This task queue MUST be run in an event loop in order for promises to be
     * settled asynchronously. It will be automatically run when synchronously
     * waiting on a promise.
     *
     * <code>
     * while ($eventLoop->isRunning()) {
     *     GuzzleHttp\Promise\Utils::queue()->run();
     * }
     * </code>
     *
     * @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
     */
    public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface
    {
        static $queue;

        if ($assign) {
            $queue = $assign;
        } elseif (!$queue) {
            $queue = new TaskQueue();
        }

        return $queue;
    }

    /**
     * Adds a function to run in the task queue when it is next `run()` and
     * returns a promise that is fulfilled or rejected with the result.
     *
     * @param callable $task Task function to run.
     */
    public static function task(callable $task): PromiseInterface
    {
        $queue = self::queue();
        $promise = new Promise([$queue, 'run']);
        $queue->add(function () use ($task, $promise): void {
            try {
                if (Is::pending($promise)) {
                    $promise->resolve($task());
                }
            } catch (\Throwable $e) {
                $promise->reject($e);
            }
        });

        return $promise;
    }

    /**
     * Synchronously waits on a promise to resolve and returns an inspection
     * state array.
     *
     * Returns a state associative array containing a "state" key mapping to a
     * valid promise state. If the state of the promise is "fulfilled", the
     * array will contain a "value" key mapping to the fulfilled value of the
     * promise. If the promise is rejected, the array will contain a "reason"
     * key mapping to the rejection reason of the promise.
     *
     * @param PromiseInterface $promise Promise or value.
     */
    public static function inspect(PromiseInterface $promise): array
    {
        try {
            return [
                'state' => PromiseInterface::FULFILLED,
                'value' => $promise->wait(),
            ];
        } catch (RejectionException $e) {
            return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
        } catch (\Throwable $e) {
            return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
        }
    }

    /**
     * Waits on all of the provided promises, but does not unwrap rejected
     * promises as thrown exception.
     *
     * Returns an array of inspection state arrays.
     *
     * @see inspect for the inspection state array format.
     *
     * @param PromiseInterface[] $promises Traversable of promises to wait upon.
     */
    public static function inspectAll($promises): array
    {
        $results = [];
        foreach ($promises as $key => $promise) {
            $results[$key] = self::inspect($promise);
        }

        return $results;
    }

    /**
     * Waits on all of the provided promises and returns the fulfilled values.
     *
     * Returns an array that contains the value of each promise (in the same
     * order the promises were provided). An exception is thrown if any of the
     * promises are rejected.
     *
     * @param iterable<PromiseInterface> $promises Iterable of PromiseInterface objects to wait on.
     *
     * @throws \Throwable on error
     */
    public static function unwrap($promises): array
    {
        $results = [];
        foreach ($promises as $key => $promise) {
            $results[$key] = $promise->wait();
        }

        return $results;
    }

    /**
     * Given an array of promises, return a promise that is fulfilled when all
     * the items in the array are fulfilled.
     *
     * The promise's fulfillment value is an array with fulfillment values at
     * respective positions to the original array. If any promise in the array
     * rejects, the returned promise is rejected with the rejection reason.
     *
     * @param mixed $promises  Promises or values.
     * @param bool  $recursive If true, resolves new promises that might have been added to the stack during its own resolution.
     */
    public static function all($promises, bool $recursive = false): PromiseInterface
    {
        $results = [];
        $promise = Each::of(
            $promises,
            function ($value, $idx) use (&$results): void {
                $results[$idx] = $value;
            },
            function ($reason, $idx, Promise $aggregate): void {
                if (Is::pending($aggregate)) {
                    $aggregate->reject($reason);
                }
            }
        )->then(function () use (&$results) {
            ksort($results);

            return $results;
        });

        if (true === $recursive) {
            $promise = $promise->then(function ($results) use ($recursive, &$promises) {
                foreach ($promises as $promise) {
                    if (Is::pending($promise)) {
                        return self::all($promises, $recursive);
                    }
                }

                return $results;
            });
        }

        return $promise;
    }

    /**
     * Initiate a competitive race between multiple promises or values (values
     * will become immediately fulfilled promises).
     *
     * When count amount of promises have been fulfilled, the returned promise
     * is fulfilled with an array that contains the fulfillment values of the
     * winners in order of resolution.
     *
     * This promise is rejected with a {@see AggregateException} if the number
     * of fulfilled promises is less than the desired $count.
     *
     * @param int   $count    Total number of promises.
     * @param mixed $promises Promises or values.
     */
    public static function some(int $count, $promises): PromiseInterface
    {
        $results = [];
        $rejections = [];

        return Each::of(
            $promises,
            function ($value, $idx, PromiseInterface $p) use (&$results, $count): void {
                if (Is::settled($p)) {
                    return;
                }
                $results[$idx] = $value;
                if (count($results) >= $count) {
                    $p->resolve(null);
                }
            },
            function ($reason) use (&$rejections): void {
                $rejections[] = $reason;
            }
        )->then(
            function () use (&$results, &$rejections, $count) {
                if (count($results) !== $count) {
                    throw new AggregateException(
                        'Not enough promises to fulfill count',
                        $rejections
                    );
                }
                ksort($results);

                return array_values($results);
            }
        );
    }

    /**
     * Like some(), with 1 as count. However, if the promise fulfills, the
     * fulfillment value is not an array of 1 but the value directly.
     *
     * @param mixed $promises Promises or values.
     */
    public static function any($promises): PromiseInterface
    {
        return self::some(1, $promises)->then(function ($values) {
            return $values[0];
        });
    }

    /**
     * Returns a promise that is fulfilled when all of the provided promises have
     * been fulfilled or rejected.
     *
     * The returned promise is fulfilled with an array of inspection state arrays.
     *
     * @see inspect for the inspection state array format.
     *
     * @param mixed $promises Promises or values.
     */
    public static function settle($promises): PromiseInterface
    {
        $results = [];

        return Each::of(
            $promises,
            function ($value, $idx) use (&$results): void {
                $results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
            },
            function ($reason, $idx) use (&$results): void {
                $results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
            }
        )->then(function () use (&$results) {
            ksort($results);

            return $results;
        });
    }
}


================================================
FILE: tests/AggregateExceptionTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\AggregateException;
use PHPUnit\Framework\TestCase;

class AggregateExceptionTest extends TestCase
{
    public function testHasReason(): void
    {
        $e = new AggregateException('foo', ['baz', 'bar']);
        $this->assertStringContainsString('foo', $e->getMessage());
        $this->assertSame(['baz', 'bar'], $e->getReason());
    }
}


================================================
FILE: tests/CoroutineTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Coroutine;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use PHPUnit\Framework\TestCase;
use ReflectionClass;

class CoroutineTest extends TestCase
{
    public function testReturnsCoroutine(): void
    {
        $fn = function () { yield 'foo'; };
        $this->assertInstanceOf(Coroutine::class, Coroutine::of($fn));
    }

    /**
     * @dataProvider promiseInterfaceMethodProvider
     *
     * @param string $method
     * @param array  $args
     */
    public function testShouldProxyPromiseMethodsToResultPromise($method, $args = []): void
    {
        $coroutine = new Coroutine(function () { yield 0; });
        $mockPromise = $this->getMockForAbstractClass(PromiseInterface::class);
        $mockPromise->expects($this->once())->method($method)->with(...$args);

        $resultPromiseProp = (new ReflectionClass(Coroutine::class))->getProperty('result');

        if (PHP_VERSION_ID < 80100) {
            $resultPromiseProp->setAccessible(true);
        }

        $resultPromiseProp->setValue($coroutine, $mockPromise);

        $coroutine->{$method}(...$args);
    }

    public static function promiseInterfaceMethodProvider()
    {
        return [
            ['then', [null, null]],
            ['otherwise', [function (): void {}]],
            ['wait', [true]],
            ['getState', []],
            ['resolve', [null]],
            ['reject', [null]],
        ];
    }

    public function testShouldCancelResultPromiseAndOutsideCurrentPromise(): void
    {
        $coroutine = new Coroutine(function () { yield 0; });

        $mockPromises = [
            'result' => $this->getMockForAbstractClass(PromiseInterface::class),
            'currentPromise' => $this->getMockForAbstractClass(PromiseInterface::class),
        ];
        foreach ($mockPromises as $propName => $mockPromise) {
            /**
             * @var \PHPUnit_Framework_MockObject_MockObject $mockPromise
             */
            $mockPromise->expects($this->once())
                ->method('cancel')
                ->with();

            $promiseProp = (new ReflectionClass(Coroutine::class))->getProperty($propName);

            if (PHP_VERSION_ID < 80100) {
                $promiseProp->setAccessible(true);
            }

            $promiseProp->setValue($coroutine, $mockPromise);
        }

        $coroutine->cancel();
    }

    public function testWaitShouldResolveChainedCoroutines(): void
    {
        $promisor = function () {
            return Coroutine::of(function () {
                yield $promise = new Promise(function () use (&$promise): void {
                    $promise->resolve(1);
                });
            });
        };

        $promise = $promisor()->then($promisor)->then($promisor);

        $this->assertSame(1, $promise->wait());
    }

    public function testWaitShouldHandleIntermediateErrors(): void
    {
        $promise = Coroutine::of(function () {
            yield $promise = new Promise(function () use (&$promise): void {
                $promise->resolve(1);
            });
        })
        ->then(function () {
            return Coroutine::of(function () {
                yield $promise = new Promise(function () use (&$promise): void {
                    $promise->reject(new \Exception());
                });
            });
        })
        ->otherwise(function (?\Exception $error = null) {
            if (!$error) {
                self::fail('Error did not propagate.');
            }

            return 3;
        });

        $this->assertSame(3, $promise->wait());
    }
}


================================================
FILE: tests/CreateTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;

class CreateTest extends TestCase
{
    public function testCreatesPromiseForValue(): void
    {
        $p = P\Create::promiseFor('foo');
        $this->assertInstanceOf(FulfilledPromise::class, $p);
    }

    public function testReturnsPromiseForPromise(): void
    {
        $p = new Promise();
        $this->assertSame($p, P\Create::promiseFor($p));
    }

    public function testReturnsPromiseForThennable(): void
    {
        $p = new Thennable();
        $wrapped = P\Create::promiseFor($p);
        $this->assertNotSame($p, $wrapped);
        $this->assertInstanceOf(PromiseInterface::class, $wrapped);
        $p->resolve('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $wrapped->wait());
    }

    public function testReturnsRejection(): void
    {
        $p = P\Create::rejectionFor('fail');
        $this->assertInstanceOf(RejectedPromise::class, $p);
        $this->assertSame('fail', PropertyHelper::get($p, 'reason'));
    }

    public function testReturnsPromisesAsIsInRejectionFor(): void
    {
        $a = new Promise();
        $b = P\Create::rejectionFor($a);
        $this->assertSame($a, $b);
    }

    public function testIterForReturnsIterator(): void
    {
        $iter = new \ArrayIterator();
        $this->assertSame($iter, P\Create::iterFor($iter));
    }
}


================================================
FILE: tests/EachPromiseTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\EachPromise;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;

/**
 * @covers \GuzzleHttp\Promise\EachPromise
 */
class EachPromiseTest extends TestCase
{
    public function testReturnsSameInstance(): void
    {
        $each = new EachPromise([], ['concurrency' => 100]);
        $this->assertSame($each->promise(), $each->promise());
    }

    public function testResolvesInCaseOfAnEmptyList(): void
    {
        $promises = [];
        $each = new EachPromise($promises);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertTrue(P\Is::fulfilled($p));
    }

    public function testResolvesInCaseOfAnEmptyListAndInvokesFulfilled(): void
    {
        $promises = [];
        $each = new EachPromise($promises);
        $p = $each->promise();
        $onFulfilledCalled = false;
        $onRejectedCalled = false;
        $p->then(
            function () use (&$onFulfilledCalled): void {
                $onFulfilledCalled = true;
            },
            function () use (&$onRejectedCalled): void {
                $onRejectedCalled = true;
            }
        );
        $this->assertNull($p->wait());
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertTrue($onFulfilledCalled);
        $this->assertFalse($onRejectedCalled);
    }

    public function testInvokesAllPromises(): void
    {
        $promises = [new Promise(), new Promise(), new Promise()];
        $called = [];
        $each = new EachPromise($promises, [
            'fulfilled' => function ($value) use (&$called): void {
                $called[] = $value;
            },
        ]);
        $p = $each->promise();
        $promises[0]->resolve('a');
        $promises[1]->resolve('c');
        $promises[2]->resolve('b');
        P\Utils::queue()->run();
        $this->assertSame(['a', 'c', 'b'], $called);
        $this->assertTrue(P\Is::fulfilled($p));
    }

    public function testIsWaitable(): void
    {
        $a = $this->createSelfResolvingPromise('a');
        $b = $this->createSelfResolvingPromise('b');
        $called = [];
        $each = new EachPromise([$a, $b], [
            'fulfilled' => function ($value) use (&$called): void { $called[] = $value; },
        ]);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertSame(['a', 'b'], $called);
    }

    public function testCanResolveBeforeConsumingAll(): void
    {
        $called = 0;
        $a = $this->createSelfResolvingPromise('a');
        $b = new Promise(function (): void { $this->fail(); });
        $each = new EachPromise([$a, $b], [
            'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called): void {
                $this->assertSame($idx, 0);
                $this->assertSame('a', $value);
                $aggregate->resolve(null);
                ++$called;
            },
            'rejected' => function (\Exception $reason): void {
                $this->fail($reason->getMessage());
            },
        ]);
        $p = $each->promise();
        $p->wait();
        $this->assertNull($p->wait());
        $this->assertSame(1, $called);
        $this->assertTrue(P\Is::fulfilled($a));
        $this->assertTrue(P\Is::pending($b));
        // Resolving $b has no effect on the aggregate promise.
        $b->resolve('foo');
        $this->assertSame(1, $called);
    }

    public function testLimitsPendingPromises(): void
    {
        $pending = [new Promise(), new Promise(), new Promise(), new Promise()];
        $promises = new \ArrayIterator($pending);
        $each = new EachPromise($promises, ['concurrency' => 2]);
        $p = $each->promise();
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        $pending[0]->resolve('a');
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        $this->assertTrue($promises->valid());
        $pending[1]->resolve('b');
        P\Utils::queue()->run();
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        $this->assertTrue($promises->valid());
        $promises[2]->resolve('c');
        P\Utils::queue()->run();
        $this->assertCount(1, PropertyHelper::get($each, 'pending'));
        $this->assertTrue(P\Is::pending($p));
        $promises[3]->resolve('d');
        P\Utils::queue()->run();
        $this->assertNull(PropertyHelper::get($each, 'pending'));
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertFalse($promises->valid());
    }

    public function testDynamicallyLimitsPendingPromises(): void
    {
        $calls = [];
        $pendingFn = function ($count) use (&$calls) {
            $calls[] = $count;

            return 2;
        };
        $pending = [new Promise(), new Promise(), new Promise(), new Promise()];
        $promises = new \ArrayIterator($pending);
        $each = new EachPromise($promises, ['concurrency' => $pendingFn]);
        $p = $each->promise();
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        $pending[0]->resolve('a');
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        $this->assertTrue($promises->valid());
        $pending[1]->resolve('b');
        $this->assertCount(2, PropertyHelper::get($each, 'pending'));
        P\Utils::queue()->run();
        $this->assertTrue($promises->valid());
        $promises[2]->resolve('c');
        P\Utils::queue()->run();
        $this->assertCount(1, PropertyHelper::get($each, 'pending'));
        $this->assertTrue(P\Is::pending($p));
        $promises[3]->resolve('d');
        P\Utils::queue()->run();
        $this->assertNull(PropertyHelper::get($each, 'pending'));
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertSame([0, 1, 1, 1], $calls);
        $this->assertFalse($promises->valid());
    }

    public function testClearsReferencesWhenResolved(): void
    {
        $called = false;
        $a = new Promise(function () use (&$a, &$called): void {
            $a->resolve('a');
            $called = true;
        });
        $each = new EachPromise([$a], [
            'concurrency' => function () { return 1; },
            'fulfilled' => function (): void {},
            'rejected' => function (): void {},
        ]);
        $each->promise()->wait();
        $this->assertNull(PropertyHelper::get($each, 'onFulfilled'));
        $this->assertNull(PropertyHelper::get($each, 'onRejected'));
        $this->assertNull(PropertyHelper::get($each, 'iterable'));
        $this->assertNull(PropertyHelper::get($each, 'pending'));
        $this->assertNull(PropertyHelper::get($each, 'concurrency'));
        $this->assertTrue($called);
    }

    public function testCanBeCancelled(): void
    {
        $called = false;
        $a = new FulfilledPromise('a');
        $b = new Promise(function () use (&$called): void { $called = true; });
        $each = new EachPromise([$a, $b], [
            'fulfilled' => function ($value, $idx, Promise $aggregate): void {
                $aggregate->cancel();
            },
            'rejected' => function ($reason) use (&$called): void {
                $called = true;
            },
        ]);
        $p = $each->promise();
        $p->wait(false);
        $this->assertTrue(P\Is::fulfilled($a));
        $this->assertTrue(P\Is::pending($b));
        $this->assertTrue(P\Is::rejected($p));
        $this->assertFalse($called);
    }

    public function testDoesNotBlowStackWithFulfilledPromises(): void
    {
        $pending = [];
        for ($i = 0; $i < 100; ++$i) {
            $pending[] = new FulfilledPromise($i);
        }
        $values = [];
        $each = new EachPromise($pending, [
            'fulfilled' => function ($value) use (&$values): void {
                $values[] = $value;
            },
        ]);
        $called = false;
        $each->promise()->then(function () use (&$called): void {
            $called = true;
        });
        $this->assertFalse($called);
        P\Utils::queue()->run();
        $this->assertTrue($called);
        $this->assertSame(range(0, 99), $values);
    }

    public function testDoesNotBlowStackWithRejectedPromises(): void
    {
        $pending = [];
        for ($i = 0; $i < 100; ++$i) {
            $pending[] = new RejectedPromise($i);
        }
        $values = [];
        $each = new EachPromise($pending, [
            'rejected' => function ($value) use (&$values): void {
                $values[] = $value;
            },
        ]);
        $called = false;
        $each->promise()->then(
            function () use (&$called): void { $called = true; },
            function (): void { $this->fail('Should not have rejected.'); }
        );
        $this->assertFalse($called);
        P\Utils::queue()->run();
        $this->assertTrue($called);
        $this->assertSame(range(0, 99), $values);
    }

    public function testReturnsPromiseForWhatever(): void
    {
        $called = [];
        $arr = ['a', 'b'];
        $each = new EachPromise($arr, [
            'fulfilled' => function ($v) use (&$called): void { $called[] = $v; },
        ]);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertSame(['a', 'b'], $called);
    }

    public function testRejectsAggregateWhenNextThrows(): void
    {
        $iter = function () {
            yield 'a';
            throw new \Exception('Failure');
        };
        $each = new EachPromise($iter());
        $p = $each->promise();
        $e = null;
        $received = null;
        $p->then(null, function ($reason) use (&$e): void { $e = $reason; });
        P\Utils::queue()->run();
        $this->assertInstanceOf(\Exception::class, $e);
        $this->assertSame('Failure', $e->getMessage());
    }

    public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting(): void
    {
        $results = [];
        $values = [10];
        $remaining = 9;
        $iter = function () use (&$values) {
            while ($value = array_pop($values)) {
                yield $value;
            }
        };
        $each = new EachPromise($iter(), [
            'concurrency' => 1,
            'fulfilled' => function ($r) use (&$results, &$values, &$remaining): void {
                $results[] = $r;
                if ($remaining > 0) {
                    $values[] = $remaining--;
                }
            },
        ]);
        $each->promise()->wait();
        $this->assertSame(range(10, 1), $results);
    }

    public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync(): void
    {
        $firstPromise = new Promise();
        $pending = [$firstPromise];
        $values = [$firstPromise];
        $results = [];
        $remaining = 9;
        $iter = function () use (&$values) {
            while ($value = array_pop($values)) {
                yield $value;
            }
        };
        $each = new EachPromise($iter(), [
            'concurrency' => 1,
            'fulfilled' => function ($r) use (&$results, &$values, &$remaining, &$pending): void {
                $results[] = $r;
                if ($remaining-- > 0) {
                    $pending[] = $values[] = new Promise();
                }
            },
        ]);
        $i = 0;
        $each->promise();
        while ($promise = array_pop($pending)) {
            $promise->resolve($i++);
            P\Utils::queue()->run();
        }
        $this->assertSame(range(0, 9), $results);
    }

    private function createSelfResolvingPromise($value)
    {
        $p = new Promise(function () use (&$p, $value): void {
            $p->resolve($value);
        });
        $trickCsFixer = true;

        return $p;
    }

    public function testMutexPreventsGeneratorRecursion(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $results = $promises = [];
        for ($i = 0; $i < 20; ++$i) {
            $p = $this->createSelfResolvingPromise($i);
            $pending[] = $p;
            $promises[] = $p;
        }

        $iter = function () use (&$promises, &$pending) {
            foreach ($promises as $promise) {
                // Resolve a promises, which will trigger the then() function,
                // which would cause the EachPromise to try to add more
                // promises to the queue. Without a lock, this would trigger
                // a "Cannot resume an already running generator" fatal error.
                if ($p = array_pop($pending)) {
                    $p->wait();
                }
                yield $promise;
            }
        };

        $each = new EachPromise($iter(), [
            'concurrency' => 5,
            'fulfilled' => function ($r) use (&$results, &$pending): void {
                $results[] = $r;
            },
        ]);

        $each->promise()->wait();
        $this->assertCount(20, $results);
    }

    public function testIteratorWithSameKey(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $iter = function () {
            yield 'foo' => $this->createSelfResolvingPromise(1);
            yield 'foo' => $this->createSelfResolvingPromise(2);
            yield 1 => $this->createSelfResolvingPromise(3);
            yield 1 => $this->createSelfResolvingPromise(4);
        };
        $called = 0;
        $each = new EachPromise($iter(), [
            'fulfilled' => function ($value, $idx, Promise $aggregate) use (&$called): void {
                ++$called;
                if ($value < 3) {
                    $this->assertSame('foo', $idx);
                } else {
                    $this->assertSame(1, $idx);
                }
            },
        ]);
        $each->promise()->wait();
        $this->assertSame(4, $called);
    }

    public function testIsWaitableWhenLimited(): void
    {
        $promises = [
            $this->createSelfResolvingPromise('a'),
            $this->createSelfResolvingPromise('c'),
            $this->createSelfResolvingPromise('b'),
            $this->createSelfResolvingPromise('d'),
        ];
        $called = [];
        $each = new EachPromise($promises, [
            'concurrency' => 2,
            'fulfilled' => function ($value) use (&$called): void {
                $called[] = $value;
            },
        ]);
        $p = $each->promise();
        $this->assertNull($p->wait());
        $this->assertSame(['a', 'c', 'b', 'd'], $called);
        $this->assertTrue(P\Is::fulfilled($p));
    }
}


================================================
FILE: tests/EachTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;

class EachTest extends TestCase
{
    public function testCallsEachLimit(): void
    {
        $p = new Promise();
        $aggregate = P\Each::ofLimit($p, 2);

        $p->resolve('a');
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::fulfilled($aggregate));
    }

    public function testEachLimitAllRejectsOnFailure(): void
    {
        $p = [new FulfilledPromise('a'), new RejectedPromise('b')];
        $aggregate = P\Each::ofLimitAll($p, 2);

        P\Utils::queue()->run();
        $this->assertTrue(P\Is::rejected($aggregate));

        $result = P\Utils::inspect($aggregate);
        $this->assertSame('b', $result['reason']);
    }
}


================================================
FILE: tests/FulfilledPromiseTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use PHPUnit\Framework\TestCase;

/**
 * @covers \GuzzleHttp\Promise\FulfilledPromise
 */
class FulfilledPromiseTest extends TestCase
{
    public function testReturnsValueWhenWaitedUpon(): void
    {
        $p = new FulfilledPromise('foo');
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertSame('foo', $p->wait(true));
    }

    public function testCannotCancel(): void
    {
        $p = new FulfilledPromise('foo');
        $this->assertTrue(P\Is::fulfilled($p));
        $p->cancel();
        $this->assertSame('foo', $p->wait());
    }

    /**
     * @expectedExceptionMessage Cannot resolve a fulfilled promise
     */
    public function testCannotResolve(): void
    {
        $this->expectException(\LogicException::class);

        $p = new FulfilledPromise('foo');
        $p->resolve('bar');
    }

    /**
     * @expectedExceptionMessage Cannot reject a fulfilled promise
     */
    public function testCannotReject(): void
    {
        $this->expectException(\LogicException::class);

        $p = new FulfilledPromise('foo');
        $p->reject('bar');
    }

    public function testCanResolveWithSameValue(): void
    {
        $p = new FulfilledPromise('foo');
        $p->resolve('foo');
        $this->assertSame('foo', $p->wait());
    }

    public function testCannotResolveWithPromise(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new FulfilledPromise(new Promise());
    }

    public function testReturnsSelfWhenNoOnFulfilled(): void
    {
        $p = new FulfilledPromise('a');
        $this->assertSame($p, $p->then());
    }

    public function testAsynchronouslyInvokesOnFulfilled(): void
    {
        $p = new FulfilledPromise('a');
        $r = null;
        $f = function ($d) use (&$r): void { $r = $d; };
        $p2 = $p->then($f);
        $this->assertNotSame($p, $p2);
        $this->assertNull($r);
        P\Utils::queue()->run();
        $this->assertSame('a', $r);
    }

    public function testReturnsNewRejectedWhenOnFulfilledFails(): void
    {
        $p = new FulfilledPromise('a');
        $f = function (): void { throw new \Exception('b'); };
        $p2 = $p->then($f);
        $this->assertNotSame($p, $p2);
        try {
            $p2->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertSame('b', $e->getMessage());
        }
    }

    public function testOtherwiseIsSugarForRejections(): void
    {
        $c = null;
        $p = new FulfilledPromise('foo');
        $p->otherwise(function ($v) use (&$c): void { $c = $v; });
        $this->assertNull($c);
    }

    public function testDoesNotTryToFulfillTwiceDuringTrampoline(): void
    {
        $fp = new FulfilledPromise('a');
        $t1 = $fp->then(function ($v) { return $v.' b'; });
        $t1->resolve('why!');
        $this->assertSame('why!', $t1->wait());
    }
}


================================================
FILE: tests/IsTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;

class IsTest extends TestCase
{
    public function testKnowsIfFulfilled(): void
    {
        $p = new FulfilledPromise(null);
        $this->assertTrue(P\Is::fulfilled($p));
        $this->assertFalse(P\Is::rejected($p));
    }

    public function testKnowsIfRejected(): void
    {
        $p = new RejectedPromise(null);
        $this->assertTrue(P\Is::rejected($p));
        $this->assertFalse(P\Is::fulfilled($p));
    }

    public function testKnowsIfSettled(): void
    {
        $p = new RejectedPromise(null);
        $this->assertTrue(P\Is::settled($p));
        $this->assertFalse(P\Is::pending($p));
    }

    public function testKnowsIfPending(): void
    {
        $p = new Promise();
        $this->assertFalse(P\Is::settled($p));
        $this->assertTrue(P\Is::pending($p));
    }
}


================================================
FILE: tests/NotPromiseInstance.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;

class NotPromiseInstance extends Thennable implements PromiseInterface
{
    private $nextPromise;

    public function __construct()
    {
        $this->nextPromise = new Promise();
    }

    public function then(?callable $res = null, ?callable $rej = null): PromiseInterface
    {
        return $this->nextPromise->then($res, $rej);
    }

    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->then($onRejected);
    }

    public function resolve($value): void
    {
        $this->nextPromise->resolve($value);
    }

    public function reject($reason): void
    {
        $this->nextPromise->reject($reason);
    }

    public function wait(bool $unwrap = true, ?bool $defaultResolution = null): void
    {
    }

    public function cancel(): void
    {
    }

    public function getState(): string
    {
        return $this->nextPromise->getState();
    }
}


================================================
FILE: tests/PromiseTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\CancellationException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\RejectionException;
use PHPUnit\Framework\TestCase;

/**
 * @covers \GuzzleHttp\Promise\Promise
 */
class PromiseTest extends TestCase
{
    public function testCannotResolveNonPendingPromise(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The promise is already fulfilled');

        $p = new Promise();
        $p->resolve('foo');
        $p->resolve('bar');
        $this->assertSame('foo', $p->wait());
    }

    public function testCanResolveWithSameValue(): void
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->resolve('foo');
        $this->assertSame('foo', $p->wait());
    }

    public function testCannotRejectNonPendingPromise(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Cannot change a fulfilled promise to rejected');

        $p = new Promise();
        $p->resolve('foo');
        $p->reject('bar');
        $this->assertSame('foo', $p->wait());
    }

    public function testCanRejectWithSameValue(): void
    {
        $p = new Promise();
        $p->reject('foo');
        $p->reject('foo');
        $this->assertTrue(P\Is::rejected($p));
    }

    public function testCannotRejectResolveWithSameValue(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Cannot change a fulfilled promise to rejected');

        $p = new Promise();
        $p->resolve('foo');
        $p->reject('foo');
    }

    public function testInvokesWaitFunction(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->resolve('10');
        });
        $this->assertSame('10', $p->wait());
    }

    public function testRejectsAndThrowsWhenWaitFailsToResolve(): void
    {
        $this->expectException(RejectionException::class);
        $this->expectExceptionMessage('The promise was rejected with reason: Invoking the wait callback did not resolve the promise');

        $p = new Promise(function (): void {});
        $p->wait();
    }

    public function testThrowsWhenUnwrapIsRejectedWithNonException(): void
    {
        $this->expectException(RejectionException::class);
        $this->expectExceptionMessage('The promise was rejected with reason: foo');

        $p = new Promise(function () use (&$p): void {
            $p->reject('foo');
        });
        $p->wait();
    }

    public function testThrowsWhenUnwrapIsRejectedWithException(): void
    {
        $this->expectException(\UnexpectedValueException::class);
        $this->expectExceptionMessage('foo');

        $e = new \UnexpectedValueException('foo');
        $p = new Promise(function () use (&$p, $e): void {
            $p->reject($e);
        });
        $p->wait();
    }

    public function testDoesNotUnwrapExceptionsWhenDisabled(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->reject('foo');
        });
        $this->assertTrue(P\Is::pending($p));
        $p->wait(false);
        $this->assertTrue(P\Is::rejected($p));
    }

    public function testRejectsSelfWhenWaitThrows(): void
    {
        $e = new \UnexpectedValueException('foo');
        $p = new Promise(function () use ($e): void {
            throw $e;
        });
        try {
            $p->wait();
            $this->fail();
        } catch (\UnexpectedValueException $e) {
            $this->assertTrue(P\Is::rejected($p));
        }
    }

    public function testWaitsOnNestedPromises(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->resolve('_');
        });
        $p2 = new Promise(function () use (&$p2): void {
            $p2->resolve('foo');
        });
        $p3 = $p->then(function () use ($p2) {
            return $p2;
        });
        $this->assertSame('foo', $p3->wait());
    }

    public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction(): void
    {
        $this->expectException(RejectionException::class);

        $p = new Promise();
        $p->wait();
    }

    public function testThrowsWaitExceptionAfterPromiseIsResolved(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->reject('Foo!');
            throw new \Exception('Bar?');
        });

        try {
            $p->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertSame('Bar?', $e->getMessage());
        }
    }

    public function testGetsActualWaitValueFromThen(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->reject('Foo!');
        });
        $p2 = $p->then(null, function ($reason) {
            return new RejectedPromise([$reason]);
        });

        try {
            $p2->wait();
            $this->fail('Should have thrown');
        } catch (RejectionException $e) {
            $this->assertSame(['Foo!'], $e->getReason());
        }
    }

    public function testWaitBehaviorIsBasedOnLastPromiseInChain(): void
    {
        $p3 = new Promise(function () use (&$p3): void {
            $p3->resolve('Whoop');
        });
        $p2 = new Promise(function () use (&$p2, $p3): void {
            $p2->reject($p3);
        });
        $p = new Promise(function () use (&$p, $p2): void {
            $p->reject($p2);
        });
        $this->assertSame('Whoop', $p->wait());
    }

    public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped(): void
    {
        $p2 = new Promise(function () use (&$p2): void {
            $p2->reject('Fail');
        });
        $p = new Promise(function () use ($p2, &$p): void {
            $p->resolve($p2);
        });
        $p->wait(false);
        $this->assertTrue(P\Is::rejected($p2));
    }

    public function testCannotCancelNonPending(): void
    {
        $p = new Promise();
        $p->resolve('foo');
        $p->cancel();
        $this->assertTrue(P\Is::fulfilled($p));
    }

    public function testCancelsPromiseWhenNoCancelFunction(): void
    {
        $this->expectException(CancellationException::class);

        $p = new Promise();
        $p->cancel();
        $this->assertTrue(P\Is::rejected($p));
        $p->wait();
    }

    public function testCancelsPromiseWithCancelFunction(): void
    {
        $called = false;
        $p = new Promise(null, function () use (&$called): void {
            $called = true;
        });
        $p->cancel();
        $this->assertTrue(P\Is::rejected($p));
        $this->assertTrue($called);
    }

    public function testCancelsUppermostPendingPromise(): void
    {
        $called = false;
        $p1 = new Promise(null, function () use (&$called): void {
            $called = true;
        });
        $p2 = $p1->then(function (): void {});
        $p3 = $p2->then(function (): void {});
        $p4 = $p3->then(function (): void {});
        $p3->cancel();
        $this->assertTrue(P\Is::rejected($p1));
        $this->assertTrue(P\Is::rejected($p2));
        $this->assertTrue(P\Is::rejected($p3));
        $this->assertTrue(P\Is::pending($p4));
        $this->assertTrue($called);

        try {
            $p3->wait();
            $this->fail();
        } catch (CancellationException $e) {
            $this->assertStringContainsString('cancelled', $e->getMessage());
        }

        try {
            $p4->wait();
            $this->fail();
        } catch (CancellationException $e) {
            $this->assertStringContainsString('cancelled', $e->getMessage());
        }

        $this->assertTrue(P\Is::rejected($p4));
    }

    public function testCancelsChildPromises(): void
    {
        $called1 = $called2 = $called3 = false;
        $p1 = new Promise(null, function () use (&$called1): void {
            $called1 = true;
        });
        $p2 = new Promise(null, function () use (&$called2): void {
            $called2 = true;
        });
        $p3 = new Promise(null, function () use (&$called3): void {
            $called3 = true;
        });
        $p4 = $p2->then(function () use ($p3) {
            return $p3;
        });
        $p5 = $p4->then(function (): void {
            $this->fail();
        });
        $p4->cancel();
        $this->assertTrue(P\Is::pending($p1));
        $this->assertTrue(P\Is::rejected($p2));
        $this->assertTrue(P\Is::pending($p3));
        $this->assertTrue(P\Is::rejected($p4));
        $this->assertTrue(P\Is::pending($p5));
        $this->assertFalse($called1);
        $this->assertTrue($called2);
        $this->assertFalse($called3);
    }

    public function testRejectsPromiseWhenCancelFails(): void
    {
        $called = false;
        $p = new Promise(null, function () use (&$called): void {
            $called = true;
            throw new \Exception('e');
        });
        $p->cancel();
        $this->assertTrue(P\Is::rejected($p));
        $this->assertTrue($called);
        try {
            $p->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertSame('e', $e->getMessage());
        }
    }

    public function testCreatesPromiseWhenFulfilledAfterThen(): void
    {
        $p = new Promise();
        $carry = null;
        $p2 = $p->then(function ($v) use (&$carry): void {
            $carry = $v;
        });
        $this->assertNotSame($p, $p2);
        $p->resolve('foo');
        P\Utils::queue()->run();

        $this->assertSame('foo', $carry);
    }

    public function testCreatesPromiseWhenFulfilledBeforeThen(): void
    {
        $p = new Promise();
        $p->resolve('foo');
        $carry = null;
        $p2 = $p->then(function ($v) use (&$carry): void {
            $carry = $v;
        });
        $this->assertNotSame($p, $p2);
        $this->assertNull($carry);
        P\Utils::queue()->run();
        $this->assertSame('foo', $carry);
    }

    public function testCreatesPromiseWhenFulfilledWithNoCallback(): void
    {
        $p = new Promise();
        $p->resolve('foo');
        $p2 = $p->then();
        $this->assertNotSame($p, $p2);
        $this->assertInstanceOf(FulfilledPromise::class, $p2);
    }

    public function testCreatesPromiseWhenRejectedAfterThen(): void
    {
        $p = new Promise();
        $carry = null;
        $p2 = $p->then(null, function ($v) use (&$carry): void {
            $carry = $v;
        });
        $this->assertNotSame($p, $p2);
        $p->reject('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $carry);
    }

    public function testCreatesPromiseWhenRejectedBeforeThen(): void
    {
        $p = new Promise();
        $p->reject('foo');
        $carry = null;
        $p2 = $p->then(null, function ($v) use (&$carry): void {
            $carry = $v;
        });
        $this->assertNotSame($p, $p2);
        $this->assertNull($carry);
        P\Utils::queue()->run();
        $this->assertSame('foo', $carry);
    }

    public function testCreatesPromiseWhenRejectedWithNoCallback(): void
    {
        $p = new Promise();
        $p->reject('foo');
        $p2 = $p->then();
        $this->assertNotSame($p, $p2);
        $this->assertInstanceOf(RejectedPromise::class, $p2);
    }

    public function testInvokesWaitFnsForThens(): void
    {
        $p = new Promise(function () use (&$p): void {
            $p->resolve('a');
        });
        $p2 = $p
            ->then(function ($v) {
                return $v.'-1-';
            })
            ->then(function ($v) {
                return $v.'2';
            });
        $this->assertSame('a-1-2', $p2->wait());
    }

    public function testStacksThenWaitFunctions(): void
    {
        $p1 = new Promise(function () use (&$p1): void {
            $p1->resolve('a');
        });
        $p2 = new Promise(function () use (&$p2): void {
            $p2->resolve('b');
        });
        $p3 = new Promise(function () use (&$p3): void {
            $p3->resolve('c');
        });
        $p4 = $p1
            ->then(function () use ($p2) {
                return $p2;
            })
            ->then(function () use ($p3) {
                return $p3;
            });
        $this->assertSame('c', $p4->wait());
    }

    public function testForwardsFulfilledDownChainBetweenGaps(): void
    {
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(function ($v) use (&$r) {
                $r = $v;

                return $v.'2';
            })
            ->then(function ($v) use (&$r2): void {
                $r2 = $v;
            });
        $p->resolve('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $r);
        $this->assertSame('foo2', $r2);
    }

    public function testForwardsRejectedPromisesDownChainBetweenGaps(): void
    {
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r) {
                $r = $v;

                return $v.'2';
            })
            ->then(function ($v) use (&$r2): void {
                $r2 = $v;
            });
        $p->reject('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $r);
        $this->assertSame('foo2', $r2);
    }

    public function testForwardsThrownPromisesDownChainBetweenGaps(): void
    {
        $e = new \Exception();
        $p = new Promise();
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r, $e): void {
                $r = $v;
                throw $e;
            })
            ->then(
                null,
                function ($v) use (&$r2): void {
                    $r2 = $v;
                }
            );
        $p->reject('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $r);
        $this->assertSame($e, $r2);
    }

    public function testForwardsReturnedRejectedPromisesDownChainBetweenGaps(): void
    {
        $p = new Promise();
        $rejected = new RejectedPromise('bar');
        $r = $r2 = null;
        $p->then(null, null)
            ->then(null, function ($v) use (&$r, $rejected) {
                $r = $v;

                return $rejected;
            })
            ->then(
                null,
                function ($v) use (&$r2): void {
                    $r2 = $v;
                }
            );
        $p->reject('foo');
        P\Utils::queue()->run();
        $this->assertSame('foo', $r);
        $this->assertSame('bar', $r2);
        try {
            $p->wait();
        } catch (RejectionException $e) {
            $this->assertSame('foo', $e->getReason());
        }
    }

    public function testForwardsHandlersToNextPromise(): void
    {
        $p = new Promise();
        $p2 = new Promise();
        $resolved = null;
        $p
            ->then(function ($v) use ($p2) {
                return $p2;
            })
            ->then(function ($value) use (&$resolved): void {
                $resolved = $value;
            });
        $p->resolve('a');
        $p2->resolve('b');
        P\Utils::queue()->run();
        $this->assertSame('b', $resolved);
    }

    public function testRemovesReferenceFromChildWhenParentWaitedUpon(): void
    {
        $r = null;
        $p = new Promise(function () use (&$p): void {
            $p->resolve('a');
        });
        $p2 = new Promise(function () use (&$p2): void {
            $p2->resolve('b');
        });
        $pb = $p->then(
            function ($v) use ($p2, &$r) {
                $r = $v;

                return $p2;
            }
        )
            ->then(function ($v) {
                return $v.'.';
            });
        $this->assertSame('a', $p->wait());
        $this->assertSame('b', $p2->wait());
        $this->assertSame('b.', $pb->wait());
        $this->assertSame('a', $r);
    }

    public function testForwardsHandlersWhenFulfilledPromiseIsReturned(): void
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->resolve('foo');
        $p2->then(function ($v) use (&$res): void {
            $res[] = 'A:'.$v;
        });
        // $res is A:foo
        $p
            ->then(function () use ($p2, &$res) {
                $res[] = 'B';

                return $p2;
            })
            ->then(function ($v) use (&$res): void {
                $res[] = 'C:'.$v;
            });
        $p->resolve('a');
        $p->then(function ($v) use (&$res): void {
            $res[] = 'D:'.$v;
        });
        P\Utils::queue()->run();
        $this->assertSame(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testForwardsHandlersWhenRejectedPromiseIsReturned(): void
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->reject('foo');
        $p2->then(null, function ($v) use (&$res): void {
            $res[] = 'A:'.$v;
        });
        $p->then(null, function () use ($p2, &$res) {
            $res[] = 'B';

            return $p2;
        })
            ->then(null, function ($v) use (&$res): void {
                $res[] = 'C:'.$v;
            });
        $p->reject('a');
        $p->then(null, function ($v) use (&$res): void {
            $res[] = 'D:'.$v;
        });
        P\Utils::queue()->run();
        $this->assertSame(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testDoesNotForwardRejectedPromise(): void
    {
        $res = [];
        $p = new Promise();
        $p2 = new Promise();
        $p2->cancel();
        $p2->then(function ($v) use (&$res) {
            $res[] = "B:$v";

            return $v;
        });
        $p->then(function ($v) use ($p2, &$res) {
            $res[] = "B:$v";

            return $p2;
        })
            ->then(function ($v) use (&$res): void {
                $res[] = 'C:'.$v;
            });
        $p->resolve('a');
        $p->then(function ($v) use (&$res): void {
            $res[] = 'D:'.$v;
        });
        P\Utils::queue()->run();
        $this->assertSame(['B:a', 'D:a'], $res);
    }

    public function testRecursivelyForwardsWhenOnlyThennable(): void
    {
        $res = [];
        $p = new Promise();
        $p2 = new Thennable();
        $p2->resolve('foo');
        $p2->then(function ($v) use (&$res): void {
            $res[] = 'A:'.$v;
        });
        $p->then(function () use ($p2, &$res) {
            $res[] = 'B';

            return $p2;
        })
            ->then(function ($v) use (&$res): void {
                $res[] = 'C:'.$v;
            });
        $p->resolve('a');
        $p->then(function ($v) use (&$res): void {
            $res[] = 'D:'.$v;
        });
        P\Utils::queue()->run();
        $this->assertSame(['A:foo', 'B', 'D:a', 'C:foo'], $res);
    }

    public function testRecursivelyForwardsWhenNotInstanceOfPromise(): void
    {
        $res = [];
        $p = new Promise();
        $p2 = new NotPromiseInstance();
        $p2->then(function ($v) use (&$res): void {
            $res[] = 'A:'.$v;
        });
        $p->then(function () use ($p2, &$res) {
            $res[] = 'B';

            return $p2;
        })
            ->then(function ($v) use (&$res): void {
                $res[] = 'C:'.$v;
            });
        $p->resolve('a');
        $p->then(function ($v) use (&$res): void {
            $res[] = 'D:'.$v;
        });
        P\Utils::queue()->run();
        $this->assertSame(['B', 'D:a'], $res);
        $p2->resolve('foo');
        P\Utils::queue()->run();
        $this->assertSame(['B', 'D:a', 'A:foo', 'C:foo'], $res);
    }

    public function testCannotResolveWithSelf(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Cannot fulfill or reject a promise with itself');

        $p = new Promise();
        $p->resolve($p);
    }

    public function testCannotRejectWithSelf(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Cannot fulfill or reject a promise with itself');

        $p = new Promise();
        $p->reject($p);
    }

    public function testDoesNotBlowStackWhenWaitingOnNestedThens(): void
    {
        $inner = new Promise(function () use (&$inner): void {
            $inner->resolve(0);
        });
        $prev = $inner;
        for ($i = 1; $i < 100; ++$i) {
            $prev = $prev->then(function ($i) {
                return $i + 1;
            });
        }

        $parent = new Promise(function () use (&$parent, $prev): void {
            $parent->resolve($prev);
        });

        $this->assertSame(99, $parent->wait());
    }

    public function testOtherwiseIsSugarForRejections(): void
    {
        $p = new Promise();
        $p->reject('foo');
        $p->otherwise(function ($v) use (&$c): void {
            $c = $v;
        });
        P\Utils::queue()->run();
        $this->assertSame($c, 'foo');
    }

    public function testRepeatedWaitFulfilled(): void
    {
        $promise = new Promise(function () use (&$promise): void {
            $promise->resolve('foo');
        });

        $this->assertSame('foo', $promise->wait());
        $this->assertSame('foo', $promise->wait());
    }

    public function testRepeatedWaitRejected(): void
    {
        $promise = new Promise(function () use (&$promise): void {
            $promise->reject(new \RuntimeException('foo'));
        });

        $exceptionCount = 0;
        try {
            $promise->wait();
        } catch (\Exception $e) {
            $this->assertSame('foo', $e->getMessage());
            ++$exceptionCount;
        }

        try {
            $promise->wait();
        } catch (\Exception $e) {
            $this->assertSame('foo', $e->getMessage());
            ++$exceptionCount;
        }

        $this->assertSame(2, $exceptionCount);
    }
}


================================================
FILE: tests/PropertyHelper.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

/**
 * A class to help get properties of an object.
 *
 * @internal
 *
 * @author Tobias Nyholm <tobias.nyholm@gmail.com>
 */
class PropertyHelper
{
    /**
     * @param object $object
     * @param string $property
     *
     * @throws \ReflectionException
     */
    public static function get($object, $property)
    {
        $property = (new \ReflectionObject($object))->getProperty($property);

        if (PHP_VERSION_ID < 80100) {
            $property->setAccessible(true);
        }

        return $property->getValue($object);
    }
}


================================================
FILE: tests/RejectedPromiseTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use PHPUnit\Framework\TestCase;

/**
 * @covers \GuzzleHttp\Promise\RejectedPromise
 */
class RejectedPromiseTest extends TestCase
{
    public function testThrowsReasonWhenWaitedUpon(): void
    {
        $p = new RejectedPromise('foo');
        $this->assertTrue(P\Is::rejected($p));
        try {
            $p->wait(true);
            $this->fail();
        } catch (\Exception $e) {
            $this->assertTrue(P\Is::rejected($p));
            $this->assertStringContainsString('foo', $e->getMessage());
        }
    }

    public function testCannotCancel(): void
    {
        $p = new RejectedPromise('foo');
        $p->cancel();
        $this->assertTrue(P\Is::rejected($p));
    }

    /**
     * @exepctedExceptionMessage Cannot resolve a rejected promise
     */
    public function testCannotResolve(): void
    {
        $this->expectException(\LogicException::class);

        $p = new RejectedPromise('foo');
        $p->resolve('bar');
    }

    /**
     * @expectedExceptionMessage Cannot reject a rejected promise
     */
    public function testCannotReject(): void
    {
        $this->expectException(\LogicException::class);

        $p = new RejectedPromise('foo');
        $p->reject('bar');
    }

    public function testCanRejectWithSameValue(): void
    {
        $p = new RejectedPromise('foo');
        $p->reject('foo');
        $this->assertTrue(P\Is::rejected($p));
    }

    public function testThrowsSpecificException(): void
    {
        $e = new \Exception();
        $p = new RejectedPromise($e);
        try {
            $p->wait(true);
            $this->fail();
        } catch (\Exception $e2) {
            $this->assertSame($e, $e2);
        }
    }

    public function testCannotResolveWithPromise(): void
    {
        $this->expectException(\InvalidArgumentException::class);

        new RejectedPromise(new Promise());
    }

    public function testReturnsSelfWhenNoOnReject(): void
    {
        $p = new RejectedPromise('a');
        $this->assertSame($p, $p->then());
    }

    public function testInvokesOnRejectedAsynchronously(): void
    {
        $p = new RejectedPromise('a');
        $r = null;
        $f = function ($reason) use (&$r): void { $r = $reason; };
        $p->then(null, $f);
        $this->assertNull($r);
        P\Utils::queue()->run();
        $this->assertSame('a', $r);
    }

    public function testReturnsNewRejectedWhenOnRejectedFails(): void
    {
        $p = new RejectedPromise('a');
        $f = function (): void { throw new \Exception('b'); };
        $p2 = $p->then(null, $f);
        $this->assertNotSame($p, $p2);
        try {
            $p2->wait();
            $this->fail();
        } catch (\Exception $e) {
            $this->assertSame('b', $e->getMessage());
        }
    }

    public function testWaitingIsNoOp(): void
    {
        $p = new RejectedPromise('a');
        $p->wait(false);
        $this->assertTrue(P\Is::rejected($p));
    }

    public function testOtherwiseIsSugarForRejections(): void
    {
        $p = new RejectedPromise('foo');
        $p->otherwise(function ($v) use (&$c): void { $c = $v; });
        P\Utils::queue()->run();
        $this->assertSame('foo', $c);
    }

    public function testCanResolveThenWithSuccess(): void
    {
        $actual = null;
        $p = new RejectedPromise('foo');
        $p->otherwise(function ($v) {
            return $v.' bar';
        })->then(function ($v) use (&$actual): void {
            $actual = $v;
        });
        P\Utils::queue()->run();
        $this->assertSame('foo bar', $actual);
    }

    public function testDoesNotTryToRejectTwiceDuringTrampoline(): void
    {
        $fp = new RejectedPromise('a');
        $t1 = $fp->then(null, function ($v) { return $v.' b'; });
        $t1->resolve('why!');
        $this->assertSame('why!', $t1->wait());
    }
}


================================================
FILE: tests/RejectionExceptionTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\RejectionException;
use PHPUnit\Framework\TestCase;

/**
 * @covers \GuzzleHttp\Promise\RejectionException
 */
class RejectionExceptionTest extends TestCase
{
    public function testCanGetReasonFromException(): void
    {
        $thing = new Thing1('foo');
        $e = new RejectionException($thing);

        $this->assertSame($thing, $e->getReason());
        $this->assertSame('The promise was rejected with reason: foo', $e->getMessage());
    }

    public function testCanGetReasonMessageFromJson(): void
    {
        $reason = new Thing2();
        $e = new RejectionException($reason);
        $this->assertStringContainsString('{}', $e->getMessage());
    }
}


================================================
FILE: tests/TaskQueueTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\TaskQueue;
use PHPUnit\Framework\TestCase;

class TaskQueueTest extends TestCase
{
    public function testKnowsIfEmpty(): void
    {
        $tq = new TaskQueue(false);
        $this->assertTrue($tq->isEmpty());
    }

    public function testKnowsIfFull(): void
    {
        $tq = new TaskQueue(false);
        $tq->add(function (): void {});
        $this->assertFalse($tq->isEmpty());
    }

    public function testExecutesTasksInOrder(): void
    {
        $tq = new TaskQueue(false);
        $called = [];
        $tq->add(function () use (&$called): void { $called[] = 'a'; });
        $tq->add(function () use (&$called): void { $called[] = 'b'; });
        $tq->add(function () use (&$called): void { $called[] = 'c'; });
        $tq->run();
        $this->assertSame(['a', 'b', 'c'], $called);
    }
}


================================================
FILE: tests/Thennable.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\Promise;

class Thennable
{
    private $nextPromise;

    public function __construct()
    {
        $this->nextPromise = new Promise();
    }

    public function then(?callable $res = null, ?callable $rej = null)
    {
        return $this->nextPromise->then($res, $rej);
    }

    public function resolve($value): void
    {
        $this->nextPromise->resolve($value);
    }
}


================================================
FILE: tests/Thing1.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

class Thing1
{
    private $message;

    public function __construct($message)
    {
        $this->message = $message;
    }

    public function __toString()
    {
        return $this->message;
    }
}


================================================
FILE: tests/Thing2.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

class Thing2 implements \JsonSerializable
{
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        return '{}';
    }
}


================================================
FILE: tests/UtilsTest.php
================================================
<?php

declare(strict_types=1);

namespace GuzzleHttp\Promise\Tests;

use GuzzleHttp\Promise\AggregateException;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\RejectionException;
use GuzzleHttp\Promise\TaskQueue;
use PHPUnit\Framework\TestCase;

class UtilsTest extends TestCase
{
    public function testWaitsOnAllPromisesIntoArray(): void
    {
        $e = new \Exception();
        $a = new Promise(function () use (&$a): void { $a->resolve('a'); });
        $b = new Promise(function () use (&$b): void { $b->reject('b'); });
        $c = new Promise(function () use (&$c, $e): void { $c->reject($e); });
        $results = P\Utils::inspectAll([$a, $b, $c]);
        $this->assertSame([
            ['state' => 'fulfilled', 'value' => 'a'],
            ['state' => 'rejected', 'reason' => 'b'],
            ['state' => 'rejected', 'reason' => $e],
        ], $results);
    }

    public function testUnwrapsPromisesWithNoDefaultAndFailure(): void
    {
        $this->expectException(RejectionException::class);

        $promises = [new FulfilledPromise('a'), new Promise()];
        P\Utils::unwrap($promises);
    }

    public function testUnwrapsPromisesWithNoDefault(): void
    {
        $promises = [new FulfilledPromise('a')];
        $this->assertSame(['a'], P\Utils::unwrap($promises));
    }

    public function testUnwrapsPromisesWithKeys(): void
    {
        $promises = [
            'foo' => new FulfilledPromise('a'),
            'bar' => new FulfilledPromise('b'),
        ];
        $this->assertSame([
            'foo' => 'a',
            'bar' => 'b',
        ], P\Utils::unwrap($promises));
    }

    public function testAllAggregatesSortedArray(): void
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = P\Utils::all([$a, $b, $c]);
        $b->resolve('b');
        $a->resolve('a');
        $c->resolve('c');
        $d->then(
            function ($value) use (&$result): void { $result = $value; },
            function ($reason) use (&$result): void { $result = $reason; }
        );
        P\Utils::queue()->run();
        $this->assertSame(['a', 'b', 'c'], $result);
    }

    public function testPromisesDynamicallyAddedToStack(): void
    {
        $promises = new \ArrayIterator();
        $counter = 0;
        $promises['a'] = new FulfilledPromise('a');
        $promises['b'] = $promise = new Promise(function () use (&$promise, &$promises, &$counter): void {
            ++$counter; // Make sure the wait function is called only once
            $promise->resolve('b');
            $promises['c'] = $subPromise = new Promise(function () use (&$subPromise): void {
                $subPromise->resolve('c');
            });
        });
        $result = P\Utils::all($promises, true)->wait();
        $this->assertCount(3, $promises);
        $this->assertCount(3, $result);
        $this->assertSame($result['c'], 'c');
        $this->assertSame(1, $counter);
    }

    public function testAllThrowsWhenAnyRejected(): void
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = P\Utils::all([$a, $b, $c]);
        $b->resolve('b');
        $a->reject('fail');
        $c->resolve('c');
        $d->then(
            function ($value) use (&$result): void { $result = $value; },
            function ($reason) use (&$result): void { $result = $reason; }
        );
        P\Utils::queue()->run();
        $this->assertSame('fail', $result);
    }

    public function testSomeAggregatesSortedArrayWithMax(): void
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = P\Utils::some(2, [$a, $b, $c]);
        $b->resolve('b');
        $c->resolve('c');
        $a->resolve('a');
        $d->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame(['b', 'c'], $result);
    }

    public function testSomeRejectsWhenTooManyRejections(): void
    {
        $a = new Promise();
        $b = new Promise();
        $d = P\Utils::some(2, [$a, $b]);
        $a->reject('bad');
        $b->resolve('good');
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::rejected($d));
        $d->then(null, function ($reason) use (&$called): void {
            $called = $reason;
        });
        P\Utils::queue()->run();
        $this->assertInstanceOf(AggregateException::class, $called);
        $this->assertContains('bad', $called->getReason());
    }

    public function testCanWaitUntilSomeCountIsSatisfied(): void
    {
        $a = new Promise(function () use (&$a): void { $a->resolve('a'); });
        $b = new Promise(function () use (&$b): void { $b->resolve('b'); });
        $c = new Promise(function () use (&$c): void { $c->resolve('c'); });
        $d = P\Utils::some(2, [$a, $b, $c]);
        $this->assertSame(['a', 'b'], $d->wait());
    }

    public function testThrowsIfImpossibleToWaitForSomeCount(): void
    {
        $this->expectException(AggregateException::class);
        $this->expectExceptionMessage('Not enough promises to fulfill count');

        $a = new Promise(function () use (&$a): void { $a->resolve('a'); });
        $d = P\Utils::some(2, [$a]);
        $d->wait();
    }

    public function testThrowsIfResolvedWithoutCountTotalResults(): void
    {
        $this->expectException(AggregateException::class);
        $this->expectExceptionMessage('Not enough promises to fulfill count');

        $a = new Promise();
        $b = new Promise();
        $d = P\Utils::some(3, [$a, $b]);
        $a->resolve('a');
        $b->resolve('b');
        $d->wait();
    }

    public function testAnyReturnsFirstMatch(): void
    {
        $a = new Promise();
        $b = new Promise();
        $c = P\Utils::any([$a, $b]);
        $b->resolve('b');
        $a->resolve('a');
        $c->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame('b', $result);
    }

    public function testSettleFulfillsWithFulfilledAndRejected(): void
    {
        $a = new Promise();
        $b = new Promise();
        $c = new Promise();
        $d = P\Utils::settle([$a, $b, $c]);
        $b->resolve('b');
        $c->resolve('c');
        $a->reject('a');
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::fulfilled($d));
        $d->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame([
            ['state' => 'rejected', 'reason' => 'a'],
            ['state' => 'fulfilled', 'value' => 'b'],
            ['state' => 'fulfilled', 'value' => 'c'],
        ], $result);
    }

    public function testCanInspectFulfilledPromise(): void
    {
        $p = new FulfilledPromise('foo');
        $this->assertSame([
            'state' => 'fulfilled',
            'value' => 'foo',
        ], P\Utils::inspect($p));
    }

    public function testCanInspectRejectedPromise(): void
    {
        $p = new RejectedPromise('foo');
        $this->assertSame([
            'state' => 'rejected',
            'reason' => 'foo',
        ], P\Utils::inspect($p));
    }

    public function testCanInspectRejectedPromiseWithNormalException(): void
    {
        $e = new \Exception('foo');
        $p = new RejectedPromise($e);
        $this->assertSame([
            'state' => 'rejected',
            'reason' => $e,
        ], P\Utils::inspect($p));
    }

    public function testReturnsTrampoline(): void
    {
        $this->assertInstanceOf(TaskQueue::class, P\Utils::queue());
        $this->assertSame(P\Utils::queue(), P\Utils::queue());
    }

    public function testCanScheduleThunk(): void
    {
        $tramp = P\Utils::queue();
        $promise = P\Utils::task(function () { return 'Hi!'; });
        $c = null;
        $promise->then(function ($v) use (&$c): void { $c = $v; });
        $this->assertNull($c);
        $tramp->run();
        $this->assertSame('Hi!', $c);
    }

    public function testCanScheduleThunkWithRejection(): void
    {
        $tramp = P\Utils::queue();
        $promise = P\Utils::task(function (): void { throw new \Exception('Hi!'); });
        $c = null;
        $promise->otherwise(function ($v) use (&$c): void { $c = $v; });
        $this->assertNull($c);
        $tramp->run();
        $this->assertSame('Hi!', $c->getMessage());
    }

    public function testCanScheduleThunkWithWait(): void
    {
        $tramp = P\Utils::queue();
        $promise = P\Utils::task(function () { return 'a'; });
        $this->assertSame('a', $promise->wait());
        $tramp->run();
    }

    public function testYieldsFromCoroutine(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = P\Coroutine::of(function () {
            $value = (yield new FulfilledPromise('a'));
            yield $value.'b';
        });
        $promise->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame('ab', $result);
    }

    public function testCanCatchExceptionsInCoroutine(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = P\Coroutine::of(function () {
            try {
                yield new RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (RejectionException $e) {
                $value = (yield new FulfilledPromise($e->getReason()));
                yield $value.'b';
            }
        });
        $promise->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::fulfilled($promise));
        $this->assertSame('ab', $result);
    }

    /**
     * @dataProvider rejectsParentExceptionProvider
     */
    public function testRejectsParentExceptionWhenException(PromiseInterface $promise): void
    {
        $promise->then(
            function (): void { $this->fail(); },
            function ($reason) use (&$result): void { $result = $reason; }
        );
        P\Utils::queue()->run();
        $this->assertInstanceOf(\Exception::class, $result);
        $this->assertSame('a', $result->getMessage());
    }

    public static function rejectsParentExceptionProvider()
    {
        return [
            [P\Coroutine::of(function () {
                yield new FulfilledPromise(0);
                throw new \Exception('a');
            })],
            [P\Coroutine::of(function () {
                throw new \Exception('a');
                yield new FulfilledPromise(0);
            })],
        ];
    }

    public function testCanRejectFromRejectionCallback(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = P\Coroutine::of(function () {
            yield new FulfilledPromise(0);
            yield new RejectedPromise('no!');
        });
        $promise->then(
            function (): void { $this->fail(); },
            function ($reason) use (&$result): void { $result = $reason; }
        );
        P\Utils::queue()->run();
        $this->assertInstanceOf(RejectionException::class, $result);
        $this->assertSame('no!', $result->getReason());
    }

    public function testCanAsyncReject(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $rej = new Promise();
        $promise = P\Coroutine::of(function () use ($rej) {
            yield new FulfilledPromise(0);
            yield $rej;
        });
        $promise->then(
            function (): void { $this->fail(); },
            function ($reason) use (&$result): void { $result = $reason; }
        );
        $rej->reject('no!');
        P\Utils::queue()->run();
        $this->assertInstanceOf(RejectionException::class, $result);
        $this->assertSame('no!', $result->getReason());
    }

    public function testCanCatchAndThrowOtherException(): void
    {
        $promise = P\Coroutine::of(function () {
            try {
                yield new RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (RejectionException $e) {
                throw new \Exception('foo');
            }
        });
        $promise->otherwise(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::rejected($promise));
        $this->assertStringContainsString('foo', $result->getMessage());
    }

    public function testCanCatchAndYieldOtherException(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = P\Coroutine::of(function () {
            try {
                yield new RejectedPromise('a');
                $this->fail('Should have thrown into the coroutine!');
            } catch (RejectionException $e) {
                yield new RejectedPromise('foo');
            }
        });
        $promise->otherwise(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertTrue(P\Is::rejected($promise));
        $this->assertStringContainsString('foo', $result->getMessage());
    }

    public function createLotsOfSynchronousPromise()
    {
        return P\Coroutine::of(function () {
            $value = 0;
            for ($i = 0; $i < 1000; ++$i) {
                $value = (yield new FulfilledPromise($i));
            }
            yield $value;
        });
    }

    public function testLotsOfSynchronousDoesNotBlowStack(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = $this->createLotsOfSynchronousPromise();
        $promise->then(function ($v) use (&$r): void { $r = $v; });
        P\Utils::queue()->run();
        $this->assertSame(999, $r);
    }

    public function testLotsOfSynchronousWaitDoesNotBlowStack(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = $this->createLotsOfSynchronousPromise();
        $promise->then(function ($v) use (&$r): void { $r = $v; });
        $this->assertSame(999, $promise->wait());
        $this->assertSame(999, $r);
    }

    private function createLotsOfFlappingPromise()
    {
        return P\Coroutine::of(function () {
            $value = 0;
            for ($i = 0; $i < 1000; ++$i) {
                try {
                    if ($i % 2) {
                        $value = (yield new FulfilledPromise($i));
                    } else {
                        $value = (yield new RejectedPromise($i));
                    }
                } catch (\Exception $e) {
                    $value = (yield new FulfilledPromise($i));
                }
            }
            yield $value;
        });
    }

    public function testLotsOfTryCatchingDoesNotBlowStack(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = $this->createLotsOfFlappingPromise();
        $promise->then(function ($v) use (&$r): void { $r = $v; });
        P\Utils::queue()->run();
        $this->assertSame(999, $r);
    }

    public function testLotsOfTryCatchingWaitingDoesNotBlowStack(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promise = $this->createLotsOfFlappingPromise();
        $promise->then(function ($v) use (&$r): void { $r = $v; });
        $this->assertSame(999, $promise->wait());
        $this->assertSame(999, $r);
    }

    public function testAsyncPromisesWithCorrectlyYieldedValues(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promises = [
            new Promise(),
            new Promise(),
            new Promise(),
        ];

        eval('
        $promise = \GuzzleHttp\Promise\Coroutine::of(function () use ($promises) {
            $value = null;
            $this->assertSame(\'skip\', (yield new \GuzzleHttp\Promise\FulfilledPromise(\'skip\')));
            foreach ($promises as $idx => $p) {
                $value = (yield $p);
                $this->assertSame($idx, $value);
                $this->assertSame(\'skip\', (yield new \GuzzleHttp\Promise\FulfilledPromise(\'skip\')));
            }
            $this->assertSame(\'skip\', (yield new \GuzzleHttp\Promise\FulfilledPromise(\'skip\')));
            yield $value;
        });
');

        $promises[0]->resolve(0);
        $promises[1]->resolve(1);
        $promises[2]->resolve(2);

        $promise->then(function ($v) use (&$r): void { $r = $v; });
        P\Utils::queue()->run();
        $this->assertSame(2, $r);
    }

    public function testYieldFinalWaitablePromise(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $p1 = new Promise(function () use (&$p1): void {
            $p1->resolve('skip me');
        });
        $p2 = new Promise(function () use (&$p2): void {
            $p2->resolve('hello!');
        });
        $co = P\Coroutine::of(function () use ($p1, $p2) {
            yield $p1;
            yield $p2;
        });
        P\Utils::queue()->run();
        $this->assertSame('hello!', $co->wait());
    }

    public function testCanYieldFinalPendingPromise(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $p1 = new Promise();
        $p2 = new Promise();
        $co = P\Coroutine::of(function () use ($p1, $p2) {
            yield $p1;
            yield $p2;
        });
        $p1->resolve('a');
        $p2->resolve('b');
        $co->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame('b', $result);
    }

    public function testCanNestYieldsAndFailures(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $p1 = new Promise();
        $p2 = new Promise();
        $p3 = new Promise();
        $p4 = new Promise();
        $p5 = new Promise();
        $co = P\Coroutine::of(function () use ($p1, $p2, $p3, $p4, $p5) {
            try {
                yield $p1;
            } catch (\Exception $e) {
                yield $p2;
                try {
                    yield $p3;
                    yield $p4;
                } catch (\Exception $e) {
                    yield $p5;
                }
            }
        });
        $p1->reject('a');
        $p2->resolve('b');
        $p3->resolve('c');
        $p4->reject('d');
        $p5->resolve('e');
        $co->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame('e', $result);
    }

    public function testCanYieldErrorsAndSuccessesWithoutRecursion(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $promises = [];
        for ($i = 0; $i < 20; ++$i) {
            $promises[] = new Promise();
        }

        $co = P\Coroutine::of(function () use ($promises) {
            for ($i = 0; $i < 20; $i += 4) {
                try {
                    yield $promises[$i];
                    yield $promises[$i + 1];
                } catch (\Exception $e) {
                    yield $promises[$i + 2];
                    yield $promises[$i + 3];
                }
            }
        });

        for ($i = 0; $i < 20; $i += 4) {
            $promises[$i]->resolve($i);
            $promises[$i + 1]->reject($i + 1);
            $promises[$i + 2]->resolve($i + 2);
            $promises[$i + 3]->resolve($i + 3);
        }

        $co->then(function ($value) use (&$result): void { $result = $value; });
        P\Utils::queue()->run();
        $this->assertSame(19, $result);
    }

    public function testCanWaitOnPromiseAfterFulfilled(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $f = function () {
            static $i = 0;
            ++$i;

            return $p = new Promise(function () use (&$p, $i): void {
                $p->resolve($i.'-bar');
            });
        };

        $promises = [];
        for ($i = 0; $i < 20; ++$i) {
            $promises[] = $f();
        }

        $p = P\Coroutine::of(function () use ($promises) {
            yield new FulfilledPromise('foo!');
            foreach ($promises as $promise) {
                yield $promise;
            }
        });

        $this->assertSame('20-bar', $p->wait());
    }

    public function testCanWaitOnErroredPromises(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $p1 = new Promise(function () use (&$p1): void { $p1->reject('a'); });
        $p2 = new Promise(function () use (&$p2): void { $p2->resolve('b'); });
        $p3 = new Promise(function () use (&$p3): void { $p3->resolve('c'); });
        $p4 = new Promise(function () use (&$p4): void { $p4->reject('d'); });
        $p5 = new Promise(function () use (&$p5): void { $p5->resolve('e'); });
        $p6 = new Promise(function () use (&$p6): void { $p6->reject('f'); });

        $co = P\Coroutine::of(function () use ($p1, $p2, $p3, $p4, $p5, $p6) {
            try {
                yield $p1;
            } catch (\Exception $e) {
                yield $p2;
                try {
                    yield $p3;
                    yield $p4;
                } catch (\Exception $e) {
                    yield $p5;
                    yield $p6;
                }
            }
        });

        $res = P\Utils::inspect($co);
        $this->assertSame('f', $res['reason']);
    }

    public function testCoroutineOtherwiseIntegrationTest(): void
    {
        if (defined('HHVM_VERSION')) {
            $this->markTestIncomplete('Broken on HHVM.');
        }

        $a = new Promise();
        $b = new Promise();
        $promise = P\Coroutine::of(function () use ($a, $b) {
            // Execute the pool of commands concurrently, and process errors.
            yield $a;
            yield $b;
        })->otherwise(function (\Exception $e): void {
            // Throw errors from the operations as a specific Multipart error.
            throw new \OutOfBoundsException('a', 0, $e);
        });
        $a->resolve('a');
        $b->reject('b');
        $reason = P\Utils::inspect($promise)['reason'];
        $this->assertInstanceOf(\OutOfBoundsException::class, $reason);
        $this->assertInstanceOf(RejectionException::class, $reason->getPrevious());
    }

    public function testCanManuallySettleTaskQueueGeneratedPromises(): void
    {
        $p1 = P\Utils::task(function () { return 'a'; });
        $p2 = P\Utils::task(function () { return 'b'; });
        $p3 = P\Utils::task(function () { return 'c'; });

        $p1->cancel();
        $p2->resolve('b2');

        $results = P\Utils::inspectAll([$p1, $p2, $p3]);

        $this->assertSame([
            ['state' => 'rejected', 'reason' => 'Promise has been cancelled'],
            ['state' => 'fulfilled', 'value' => 'b2'],
            ['state' => 'fulfilled', 'value' => 'c'],
        ], $results);
    }
}


================================================
FILE: vendor-bin/php-cs-fixer/composer.json
================================================
{
    "require": {
        "php": "^7.4",
        "friendsofphp/php-cs-fixer": "3.94.2"
    },
    "config": {
        "preferred-install": "dist"
    }
}


================================================
FILE: vendor-bin/phpstan/composer.json
================================================
{
    "require": {
        "php": "^8.2",
        "phpstan/phpstan": "2.1.40",
        "phpstan/phpstan-deprecation-rules": "2.0.3"
    },
    "config": {
        "preferred-install": "dist"
    }
}
Download .txt
gitextract_7g2oiihb/

├── .editorconfig
├── .gitattributes
├── .github/
│   ├── .editorconfig
│   ├── FUNDING.yml
│   ├── stale.yml
│   └── workflows/
│       ├── checks.yml
│       ├── ci.yml
│       └── static.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── composer.json
├── phpstan-baseline.neon
├── phpstan.neon.dist
├── phpunit.xml.dist
├── 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
├── tests/
│   ├── AggregateExceptionTest.php
│   ├── CoroutineTest.php
│   ├── CreateTest.php
│   ├── EachPromiseTest.php
│   ├── EachTest.php
│   ├── FulfilledPromiseTest.php
│   ├── IsTest.php
│   ├── NotPromiseInstance.php
│   ├── PromiseTest.php
│   ├── PropertyHelper.php
│   ├── RejectedPromiseTest.php
│   ├── RejectionExceptionTest.php
│   ├── TaskQueueTest.php
│   ├── Thennable.php
│   ├── Thing1.php
│   ├── Thing2.php
│   └── UtilsTest.php
└── vendor-bin/
    ├── php-cs-fixer/
    │   └── composer.json
    └── phpstan/
        └── composer.json
Download .txt
SYMBOL INDEX (297 symbols across 33 files)

FILE: src/AggregateException.php
  class AggregateException (line 10) | class AggregateException extends RejectionException
    method __construct (line 12) | public function __construct(string $msg, array $reasons)

FILE: src/CancellationException.php
  class CancellationException (line 10) | class CancellationException extends RejectionException

FILE: src/Coroutine.php
  class Coroutine (line 46) | final class Coroutine implements PromiseInterface
    method __construct (line 63) | public function __construct(callable $generatorFn)
    method of (line 81) | public static function of(callable $generatorFn): self
    method then (line 86) | public function then(
    method otherwise (line 93) | public function otherwise(callable $onRejected): PromiseInterface
    method wait (line 98) | public function wait(bool $unwrap = true)
    method getState (line 103) | public function getState(): string
    method resolve (line 108) | public function resolve($value): void
    method reject (line 113) | public function reject($reason): void
    method cancel (line 118) | public function cancel(): void
    method nextCoroutine (line 124) | private function nextCoroutine($yielded): void
    method _handleSuccess (line 133) | public function _handleSuccess($value): void
    method _handleFailure (line 151) | public function _handleFailure($reason): void

FILE: src/Create.php
  class Create (line 7) | final class Create
    method promiseFor (line 14) | public static function promiseFor($value): PromiseInterface
    method rejectionFor (line 39) | public static function rejectionFor($reason): PromiseInterface
    method exceptionFor (line 53) | public static function exceptionFor($reason): \Throwable
    method iterFor (line 67) | public static function iterFor($value): \Iterator

FILE: src/Each.php
  class Each (line 7) | final class Each
    method of (line 24) | public static function of(
    method ofLimit (line 46) | public static function ofLimit(
    method ofLimitAll (line 67) | public static function ofLimitAll(

FILE: src/EachPromise.php
  class EachPromise (line 13) | class EachPromise implements PromisorInterface
    method __construct (line 58) | public function __construct($iterable, array $config = [])
    method promise (line 76) | public function promise(): PromiseInterface
    method createPromise (line 97) | private function createPromise(): void
    method refillPending (line 126) | private function refillPending(): void
    method addPending (line 157) | private function addPending(): bool
    method advanceIterator (line 196) | private function advanceIterator(): bool
    method step (line 219) | private function step(int $idx): void
    method checkIfFinished (line 237) | private function checkIfFinished(): bool

FILE: src/FulfilledPromise.php
  class FulfilledPromise (line 15) | class FulfilledPromise implements PromiseInterface
    method __construct (line 22) | public function __construct($value)
    method then (line 33) | public function then(
    method otherwise (line 58) | public function otherwise(callable $onRejected): PromiseInterface
    method wait (line 63) | public function wait(bool $unwrap = true)
    method getState (line 68) | public function getState(): string
    method resolve (line 73) | public function resolve($value): void
    method reject (line 80) | public function reject($reason): void
    method cancel (line 85) | public function cancel(): void

FILE: src/Is.php
  class Is (line 7) | final class Is
    method pending (line 12) | public static function pending(PromiseInterface $promise): bool
    method settled (line 20) | public static function settled(PromiseInterface $promise): bool
    method fulfilled (line 28) | public static function fulfilled(PromiseInterface $promise): bool
    method rejected (line 36) | public static function rejected(PromiseInterface $promise): bool

FILE: src/Promise.php
  class Promise (line 14) | class Promise implements PromiseInterface
    method __construct (line 27) | public function __construct(
    method then (line 35) | public function then(
    method otherwise (line 62) | public function otherwise(callable $onRejected): PromiseInterface
    method wait (line 67) | public function wait(bool $unwrap = true)
    method getState (line 83) | public function getState(): string
    method cancel (line 88) | public function cancel(): void
    method resolve (line 113) | public function resolve($value): void
    method reject (line 118) | public function reject($reason): void
    method settle (line 123) | private function settle(string $state, $value): void
    method callHandler (line 188) | private static function callHandler(int $index, $value, array $handler...
    method waitIfPending (line 222) | private function waitIfPending(): void
    method invokeWaitFn (line 246) | private function invokeWaitFn(): void
    method invokeWaitList (line 265) | private function invokeWaitList(): void

FILE: src/PromiseInterface.php
  type PromiseInterface (line 16) | interface PromiseInterface
    method then (line 29) | public function then(
    method otherwise (line 42) | public function otherwise(callable $onRejected): PromiseInterface;
    method getState (line 50) | public function getState(): string;
    method resolve (line 59) | public function resolve($value): void;
    method reject (line 68) | public function reject($reason): void;
    method cancel (line 75) | public function cancel(): void;
    method wait (line 90) | public function wait(bool $unwrap = true);

FILE: src/PromisorInterface.php
  type PromisorInterface (line 10) | interface PromisorInterface
    method promise (line 15) | public function promise(): PromiseInterface;

FILE: src/RejectedPromise.php
  class RejectedPromise (line 15) | class RejectedPromise implements PromiseInterface
    method __construct (line 22) | public function __construct($reason)
    method then (line 33) | public function then(
    method otherwise (line 60) | public function otherwise(callable $onRejected): PromiseInterface
    method wait (line 65) | public function wait(bool $unwrap = true)
    method getState (line 74) | public function getState(): string
    method resolve (line 79) | public function resolve($value): void
    method reject (line 84) | public function reject($reason): void
    method cancel (line 91) | public function cancel(): void

FILE: src/RejectionException.php
  class RejectionException (line 12) | class RejectionException extends \RuntimeException
    method __construct (line 21) | public function __construct($reason, ?string $description = null)
    method getReason (line 45) | public function getReason()

FILE: src/TaskQueue.php
  class TaskQueue (line 18) | class TaskQueue implements TaskQueueInterface
    method __construct (line 23) | public function __construct(bool $withShutdown = true)
    method isEmpty (line 38) | public function isEmpty(): bool
    method add (line 43) | public function add(callable $task): void
    method run (line 48) | public function run(): void
    method disableShutdown (line 67) | public function disableShutdown(): void

FILE: src/TaskQueueInterface.php
  type TaskQueueInterface (line 7) | interface TaskQueueInterface
    method isEmpty (line 12) | public function isEmpty(): bool;
    method add (line 18) | public function add(callable $task): void;
    method run (line 23) | public function run(): void;

FILE: src/Utils.php
  class Utils (line 7) | final class Utils
    method queue (line 24) | public static function queue(?TaskQueueInterface $assign = null): Task...
    method task (line 43) | public static function task(callable $task): PromiseInterface
    method inspect (line 72) | public static function inspect(PromiseInterface $promise): array
    method inspectAll (line 96) | public static function inspectAll($promises): array
    method unwrap (line 117) | public static function unwrap($promises): array
    method all (line 138) | public static function all($promises, bool $recursive = false): Promis...
    method some (line 186) | public static function some(int $count, $promises): PromiseInterface
    method any (line 226) | public static function any($promises): PromiseInterface
    method settle (line 243) | public static function settle($promises): PromiseInterface

FILE: tests/AggregateExceptionTest.php
  class AggregateExceptionTest (line 10) | class AggregateExceptionTest extends TestCase
    method testHasReason (line 12) | public function testHasReason(): void

FILE: tests/CoroutineTest.php
  class CoroutineTest (line 13) | class CoroutineTest extends TestCase
    method testReturnsCoroutine (line 15) | public function testReturnsCoroutine(): void
    method testShouldProxyPromiseMethodsToResultPromise (line 27) | public function testShouldProxyPromiseMethodsToResultPromise($method, ...
    method promiseInterfaceMethodProvider (line 44) | public static function promiseInterfaceMethodProvider()
    method testShouldCancelResultPromiseAndOutsideCurrentPromise (line 56) | public function testShouldCancelResultPromiseAndOutsideCurrentPromise(...
    method testWaitShouldResolveChainedCoroutines (line 84) | public function testWaitShouldResolveChainedCoroutines(): void
    method testWaitShouldHandleIntermediateErrors (line 99) | public function testWaitShouldHandleIntermediateErrors(): void

FILE: tests/CreateTest.php
  class CreateTest (line 14) | class CreateTest extends TestCase
    method testCreatesPromiseForValue (line 16) | public function testCreatesPromiseForValue(): void
    method testReturnsPromiseForPromise (line 22) | public function testReturnsPromiseForPromise(): void
    method testReturnsPromiseForThennable (line 28) | public function testReturnsPromiseForThennable(): void
    method testReturnsRejection (line 39) | public function testReturnsRejection(): void
    method testReturnsPromisesAsIsInRejectionFor (line 46) | public function testReturnsPromisesAsIsInRejectionFor(): void
    method testIterForReturnsIterator (line 53) | public function testIterForReturnsIterator(): void

FILE: tests/EachPromiseTest.php
  class EachPromiseTest (line 17) | class EachPromiseTest extends TestCase
    method testReturnsSameInstance (line 19) | public function testReturnsSameInstance(): void
    method testResolvesInCaseOfAnEmptyList (line 25) | public function testResolvesInCaseOfAnEmptyList(): void
    method testResolvesInCaseOfAnEmptyListAndInvokesFulfilled (line 34) | public function testResolvesInCaseOfAnEmptyListAndInvokesFulfilled(): ...
    method testInvokesAllPromises (line 55) | public function testInvokesAllPromises(): void
    method testIsWaitable (line 73) | public function testIsWaitable(): void
    method testCanResolveBeforeConsumingAll (line 87) | public function testCanResolveBeforeConsumingAll(): void
    method testLimitsPendingPromises (line 114) | public function testLimitsPendingPromises(): void
    method testDynamicallyLimitsPendingPromises (line 139) | public function testDynamicallyLimitsPendingPromises(): void
    method testClearsReferencesWhenResolved (line 171) | public function testClearsReferencesWhenResolved(): void
    method testCanBeCancelled (line 192) | public function testCanBeCancelled(): void
    method testDoesNotBlowStackWithFulfilledPromises (line 213) | public function testDoesNotBlowStackWithFulfilledPromises(): void
    method testDoesNotBlowStackWithRejectedPromises (line 235) | public function testDoesNotBlowStackWithRejectedPromises(): void
    method testReturnsPromiseForWhatever (line 258) | public function testReturnsPromiseForWhatever(): void
    method testRejectsAggregateWhenNextThrows (line 270) | public function testRejectsAggregateWhenNextThrows(): void
    method testDoesNotCallNextOnIteratorUntilNeededWhenWaiting (line 286) | public function testDoesNotCallNextOnIteratorUntilNeededWhenWaiting():...
    method testDoesNotCallNextOnIteratorUntilNeededWhenAsync (line 309) | public function testDoesNotCallNextOnIteratorUntilNeededWhenAsync(): void
    method createSelfResolvingPromise (line 339) | private function createSelfResolvingPromise($value)
    method testMutexPreventsGeneratorRecursion (line 349) | public function testMutexPreventsGeneratorRecursion(): void
    method testIteratorWithSameKey (line 386) | public function testIteratorWithSameKey(): void
    method testIsWaitableWhenLimited (line 413) | public function testIsWaitableWhenLimited(): void

FILE: tests/EachTest.php
  class EachTest (line 13) | class EachTest extends TestCase
    method testCallsEachLimit (line 15) | public function testCallsEachLimit(): void
    method testEachLimitAllRejectsOnFailure (line 25) | public function testEachLimitAllRejectsOnFailure(): void

FILE: tests/FulfilledPromiseTest.php
  class FulfilledPromiseTest (line 15) | class FulfilledPromiseTest extends TestCase
    method testReturnsValueWhenWaitedUpon (line 17) | public function testReturnsValueWhenWaitedUpon(): void
    method testCannotCancel (line 24) | public function testCannotCancel(): void
    method testCannotResolve (line 35) | public function testCannotResolve(): void
    method testCannotReject (line 46) | public function testCannotReject(): void
    method testCanResolveWithSameValue (line 54) | public function testCanResolveWithSameValue(): void
    method testCannotResolveWithPromise (line 61) | public function testCannotResolveWithPromise(): void
    method testReturnsSelfWhenNoOnFulfilled (line 68) | public function testReturnsSelfWhenNoOnFulfilled(): void
    method testAsynchronouslyInvokesOnFulfilled (line 74) | public function testAsynchronouslyInvokesOnFulfilled(): void
    method testReturnsNewRejectedWhenOnFulfilledFails (line 86) | public function testReturnsNewRejectedWhenOnFulfilledFails(): void
    method testOtherwiseIsSugarForRejections (line 100) | public function testOtherwiseIsSugarForRejections(): void
    method testDoesNotTryToFulfillTwiceDuringTrampoline (line 108) | public function testDoesNotTryToFulfillTwiceDuringTrampoline(): void

FILE: tests/IsTest.php
  class IsTest (line 13) | class IsTest extends TestCase
    method testKnowsIfFulfilled (line 15) | public function testKnowsIfFulfilled(): void
    method testKnowsIfRejected (line 22) | public function testKnowsIfRejected(): void
    method testKnowsIfSettled (line 29) | public function testKnowsIfSettled(): void
    method testKnowsIfPending (line 36) | public function testKnowsIfPending(): void

FILE: tests/NotPromiseInstance.php
  class NotPromiseInstance (line 10) | class NotPromiseInstance extends Thennable implements PromiseInterface
    method __construct (line 14) | public function __construct()
    method then (line 19) | public function then(?callable $res = null, ?callable $rej = null): Pr...
    method otherwise (line 24) | public function otherwise(callable $onRejected): PromiseInterface
    method resolve (line 29) | public function resolve($value): void
    method reject (line 34) | public function reject($reason): void
    method wait (line 39) | public function wait(bool $unwrap = true, ?bool $defaultResolution = n...
    method cancel (line 43) | public function cancel(): void
    method getState (line 47) | public function getState(): string

FILE: tests/PromiseTest.php
  class PromiseTest (line 18) | class PromiseTest extends TestCase
    method testCannotResolveNonPendingPromise (line 20) | public function testCannotResolveNonPendingPromise(): void
    method testCanResolveWithSameValue (line 31) | public function testCanResolveWithSameValue(): void
    method testCannotRejectNonPendingPromise (line 39) | public function testCannotRejectNonPendingPromise(): void
    method testCanRejectWithSameValue (line 50) | public function testCanRejectWithSameValue(): void
    method testCannotRejectResolveWithSameValue (line 58) | public function testCannotRejectResolveWithSameValue(): void
    method testInvokesWaitFunction (line 68) | public function testInvokesWaitFunction(): void
    method testRejectsAndThrowsWhenWaitFailsToResolve (line 76) | public function testRejectsAndThrowsWhenWaitFailsToResolve(): void
    method testThrowsWhenUnwrapIsRejectedWithNonException (line 85) | public function testThrowsWhenUnwrapIsRejectedWithNonException(): void
    method testThrowsWhenUnwrapIsRejectedWithException (line 96) | public function testThrowsWhenUnwrapIsRejectedWithException(): void
    method testDoesNotUnwrapExceptionsWhenDisabled (line 108) | public function testDoesNotUnwrapExceptionsWhenDisabled(): void
    method testRejectsSelfWhenWaitThrows (line 118) | public function testRejectsSelfWhenWaitThrows(): void
    method testWaitsOnNestedPromises (line 132) | public function testWaitsOnNestedPromises(): void
    method testThrowsWhenWaitingOnPromiseWithNoWaitFunction (line 146) | public function testThrowsWhenWaitingOnPromiseWithNoWaitFunction(): void
    method testThrowsWaitExceptionAfterPromiseIsResolved (line 154) | public function testThrowsWaitExceptionAfterPromiseIsResolved(): void
    method testGetsActualWaitValueFromThen (line 169) | public function testGetsActualWaitValueFromThen(): void
    method testWaitBehaviorIsBasedOnLastPromiseInChain (line 186) | public function testWaitBehaviorIsBasedOnLastPromiseInChain(): void
    method testWaitsOnAPromiseChainEvenWhenNotUnwrapped (line 200) | public function testWaitsOnAPromiseChainEvenWhenNotUnwrapped(): void
    method testCannotCancelNonPending (line 212) | public function testCannotCancelNonPending(): void
    method testCancelsPromiseWhenNoCancelFunction (line 220) | public function testCancelsPromiseWhenNoCancelFunction(): void
    method testCancelsPromiseWithCancelFunction (line 230) | public function testCancelsPromiseWithCancelFunction(): void
    method testCancelsUppermostPendingPromise (line 241) | public function testCancelsUppermostPendingPromise(): void
    method testCancelsChildPromises (line 274) | public function testCancelsChildPromises(): void
    method testRejectsPromiseWhenCancelFails (line 303) | public function testRejectsPromiseWhenCancelFails(): void
    method testCreatesPromiseWhenFulfilledAfterThen (line 321) | public function testCreatesPromiseWhenFulfilledAfterThen(): void
    method testCreatesPromiseWhenFulfilledBeforeThen (line 335) | public function testCreatesPromiseWhenFulfilledBeforeThen(): void
    method testCreatesPromiseWhenFulfilledWithNoCallback (line 349) | public function testCreatesPromiseWhenFulfilledWithNoCallback(): void
    method testCreatesPromiseWhenRejectedAfterThen (line 358) | public function testCreatesPromiseWhenRejectedAfterThen(): void
    method testCreatesPromiseWhenRejectedBeforeThen (line 371) | public function testCreatesPromiseWhenRejectedBeforeThen(): void
    method testCreatesPromiseWhenRejectedWithNoCallback (line 385) | public function testCreatesPromiseWhenRejectedWithNoCallback(): void
    method testInvokesWaitFnsForThens (line 394) | public function testInvokesWaitFnsForThens(): void
    method testStacksThenWaitFunctions (line 409) | public function testStacksThenWaitFunctions(): void
    method testForwardsFulfilledDownChainBetweenGaps (line 430) | public function testForwardsFulfilledDownChainBetweenGaps(): void
    method testForwardsRejectedPromisesDownChainBetweenGaps (line 449) | public function testForwardsRejectedPromisesDownChainBetweenGaps(): void
    method testForwardsThrownPromisesDownChainBetweenGaps (line 468) | public function testForwardsThrownPromisesDownChainBetweenGaps(): void
    method testForwardsReturnedRejectedPromisesDownChainBetweenGaps (line 490) | public function testForwardsReturnedRejectedPromisesDownChainBetweenGa...
    method testForwardsHandlersToNextPromise (line 518) | public function testForwardsHandlersToNextPromise(): void
    method testRemovesReferenceFromChildWhenParentWaitedUpon (line 536) | public function testRemovesReferenceFromChildWhenParentWaitedUpon(): void
    method testForwardsHandlersWhenFulfilledPromiseIsReturned (line 561) | public function testForwardsHandlersWhenFulfilledPromiseIsReturned(): ...
    method testForwardsHandlersWhenRejectedPromiseIsReturned (line 588) | public function testForwardsHandlersWhenRejectedPromiseIsReturned(): void
    method testDoesNotForwardRejectedPromise (line 613) | public function testDoesNotForwardRejectedPromise(): void
    method testRecursivelyForwardsWhenOnlyThennable (line 640) | public function testRecursivelyForwardsWhenOnlyThennable(): void
    method testRecursivelyForwardsWhenNotInstanceOfPromise (line 665) | public function testRecursivelyForwardsWhenNotInstanceOfPromise(): void
    method testCannotResolveWithSelf (line 692) | public function testCannotResolveWithSelf(): void
    method testCannotRejectWithSelf (line 701) | public function testCannotRejectWithSelf(): void
    method testDoesNotBlowStackWhenWaitingOnNestedThens (line 710) | public function testDoesNotBlowStackWhenWaitingOnNestedThens(): void
    method testOtherwiseIsSugarForRejections (line 729) | public function testOtherwiseIsSugarForRejections(): void
    method testRepeatedWaitFulfilled (line 740) | public function testRepeatedWaitFulfilled(): void
    method testRepeatedWaitRejected (line 750) | public function testRepeatedWaitRejected(): void

FILE: tests/PropertyHelper.php
  class PropertyHelper (line 14) | class PropertyHelper
    method get (line 22) | public static function get($object, $property)

FILE: tests/RejectedPromiseTest.php
  class RejectedPromiseTest (line 15) | class RejectedPromiseTest extends TestCase
    method testThrowsReasonWhenWaitedUpon (line 17) | public function testThrowsReasonWhenWaitedUpon(): void
    method testCannotCancel (line 30) | public function testCannotCancel(): void
    method testCannotResolve (line 40) | public function testCannotResolve(): void
    method testCannotReject (line 51) | public function testCannotReject(): void
    method testCanRejectWithSameValue (line 59) | public function testCanRejectWithSameValue(): void
    method testThrowsSpecificException (line 66) | public function testThrowsSpecificException(): void
    method testCannotResolveWithPromise (line 78) | public function testCannotResolveWithPromise(): void
    method testReturnsSelfWhenNoOnReject (line 85) | public function testReturnsSelfWhenNoOnReject(): void
    method testInvokesOnRejectedAsynchronously (line 91) | public function testInvokesOnRejectedAsynchronously(): void
    method testReturnsNewRejectedWhenOnRejectedFails (line 102) | public function testReturnsNewRejectedWhenOnRejectedFails(): void
    method testWaitingIsNoOp (line 116) | public function testWaitingIsNoOp(): void
    method testOtherwiseIsSugarForRejections (line 123) | public function testOtherwiseIsSugarForRejections(): void
    method testCanResolveThenWithSuccess (line 131) | public function testCanResolveThenWithSuccess(): void
    method testDoesNotTryToRejectTwiceDuringTrampoline (line 144) | public function testDoesNotTryToRejectTwiceDuringTrampoline(): void

FILE: tests/RejectionExceptionTest.php
  class RejectionExceptionTest (line 13) | class RejectionExceptionTest extends TestCase
    method testCanGetReasonFromException (line 15) | public function testCanGetReasonFromException(): void
    method testCanGetReasonMessageFromJson (line 24) | public function testCanGetReasonMessageFromJson(): void

FILE: tests/TaskQueueTest.php
  class TaskQueueTest (line 10) | class TaskQueueTest extends TestCase
    method testKnowsIfEmpty (line 12) | public function testKnowsIfEmpty(): void
    method testKnowsIfFull (line 18) | public function testKnowsIfFull(): void
    method testExecutesTasksInOrder (line 25) | public function testExecutesTasksInOrder(): void

FILE: tests/Thennable.php
  class Thennable (line 9) | class Thennable
    method __construct (line 13) | public function __construct()
    method then (line 18) | public function then(?callable $res = null, ?callable $rej = null)
    method resolve (line 23) | public function resolve($value): void

FILE: tests/Thing1.php
  class Thing1 (line 7) | class Thing1
    method __construct (line 11) | public function __construct($message)
    method __toString (line 16) | public function __toString()

FILE: tests/Thing2.php
  class Thing2 (line 7) | class Thing2 implements \JsonSerializable
    method jsonSerialize (line 9) | #[\ReturnTypeWillChange]

FILE: tests/UtilsTest.php
  class UtilsTest (line 17) | class UtilsTest extends TestCase
    method testWaitsOnAllPromisesIntoArray (line 19) | public function testWaitsOnAllPromisesIntoArray(): void
    method testUnwrapsPromisesWithNoDefaultAndFailure (line 33) | public function testUnwrapsPromisesWithNoDefaultAndFailure(): void
    method testUnwrapsPromisesWithNoDefault (line 41) | public function testUnwrapsPromisesWithNoDefault(): void
    method testUnwrapsPromisesWithKeys (line 47) | public function testUnwrapsPromisesWithKeys(): void
    method testAllAggregatesSortedArray (line 59) | public function testAllAggregatesSortedArray(): void
    method testPromisesDynamicallyAddedToStack (line 76) | public function testPromisesDynamicallyAddedToStack(): void
    method testAllThrowsWhenAnyRejected (line 95) | public function testAllThrowsWhenAnyRejected(): void
    method testSomeAggregatesSortedArrayWithMax (line 112) | public function testSomeAggregatesSortedArrayWithMax(): void
    method testSomeRejectsWhenTooManyRejections (line 126) | public function testSomeRejectsWhenTooManyRejections(): void
    method testCanWaitUntilSomeCountIsSatisfied (line 143) | public function testCanWaitUntilSomeCountIsSatisfied(): void
    method testThrowsIfImpossibleToWaitForSomeCount (line 152) | public function testThrowsIfImpossibleToWaitForSomeCount(): void
    method testThrowsIfResolvedWithoutCountTotalResults (line 162) | public function testThrowsIfResolvedWithoutCountTotalResults(): void
    method testAnyReturnsFirstMatch (line 175) | public function testAnyReturnsFirstMatch(): void
    method testSettleFulfillsWithFulfilledAndRejected (line 187) | public function testSettleFulfillsWithFulfilledAndRejected(): void
    method testCanInspectFulfilledPromise (line 207) | public function testCanInspectFulfilledPromise(): void
    method testCanInspectRejectedPromise (line 216) | public function testCanInspectRejectedPromise(): void
    method testCanInspectRejectedPromiseWithNormalException (line 225) | public function testCanInspectRejectedPromiseWithNormalException(): void
    method testReturnsTrampoline (line 235) | public function testReturnsTrampoline(): void
    method testCanScheduleThunk (line 241) | public function testCanScheduleThunk(): void
    method testCanScheduleThunkWithRejection (line 252) | public function testCanScheduleThunkWithRejection(): void
    method testCanScheduleThunkWithWait (line 263) | public function testCanScheduleThunkWithWait(): void
    method testYieldsFromCoroutine (line 271) | public function testYieldsFromCoroutine(): void
    method testCanCatchExceptionsInCoroutine (line 286) | public function testCanCatchExceptionsInCoroutine(): void
    method testRejectsParentExceptionWhenException (line 310) | public function testRejectsParentExceptionWhenException(PromiseInterfa...
    method rejectsParentExceptionProvider (line 321) | public static function rejectsParentExceptionProvider()
    method testCanRejectFromRejectionCallback (line 335) | public function testCanRejectFromRejectionCallback(): void
    method testCanAsyncReject (line 354) | public function testCanAsyncReject(): void
    method testCanCatchAndThrowOtherException (line 375) | public function testCanCatchAndThrowOtherException(): void
    method testCanCatchAndYieldOtherException (line 391) | public function testCanCatchAndYieldOtherException(): void
    method createLotsOfSynchronousPromise (line 411) | public function createLotsOfSynchronousPromise()
    method testLotsOfSynchronousDoesNotBlowStack (line 422) | public function testLotsOfSynchronousDoesNotBlowStack(): void
    method testLotsOfSynchronousWaitDoesNotBlowStack (line 434) | public function testLotsOfSynchronousWaitDoesNotBlowStack(): void
    method createLotsOfFlappingPromise (line 446) | private function createLotsOfFlappingPromise()
    method testLotsOfTryCatchingDoesNotBlowStack (line 465) | public function testLotsOfTryCatchingDoesNotBlowStack(): void
    method testLotsOfTryCatchingWaitingDoesNotBlowStack (line 477) | public function testLotsOfTryCatchingWaitingDoesNotBlowStack(): void
    method testAsyncPromisesWithCorrectlyYieldedValues (line 489) | public function testAsyncPromisesWithCorrectlyYieldedValues(): void
    method testYieldFinalWaitablePromise (line 524) | public function testYieldFinalWaitablePromise(): void
    method testCanYieldFinalPendingPromise (line 544) | public function testCanYieldFinalPendingPromise(): void
    method testCanNestYieldsAndFailures (line 563) | public function testCanNestYieldsAndFailures(): void
    method testCanYieldErrorsAndSuccessesWithoutRecursion (line 597) | public function testCanYieldErrorsAndSuccessesWithoutRecursion(): void
    method testCanWaitOnPromiseAfterFulfilled (line 632) | public function testCanWaitOnPromiseAfterFulfilled(): void
    method testCanWaitOnErroredPromises (line 662) | public function testCanWaitOnErroredPromises(): void
    method testCoroutineOtherwiseIntegrationTest (line 694) | public function testCoroutineOtherwiseIntegrationTest(): void
    method testCanManuallySettleTaskQueueGeneratedPromises (line 717) | public function testCanManuallySettleTaskQueueGeneratedPromises(): void
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (165K chars).
[
  {
    "path": ".editorconfig",
    "chars": 147,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".gitattributes",
    "chars": 494,
    "preview": ".editorconfig           export-ignore\n.gitattributes          export-ignore\n/.github/               export-ignore\n.gitig"
  },
  {
    "path": ".github/.editorconfig",
    "chars": 24,
    "preview": "[*.yml]\nindent_size = 2\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 75,
    "preview": "github: [Nyholm, GrahamCampbell]\ntidelift: \"packagist/guzzlehttp/promises\"\n"
  },
  {
    "path": ".github/stale.yml",
    "chars": 557,
    "preview": "daysUntilStale: 120\ndaysUntilClose: 14\nexemptLabels:\n  - lifecycle/keep-open\n  - lifecycle/ready-for-merge\n# Label to us"
  },
  {
    "path": ".github/workflows/checks.yml",
    "chars": 337,
    "preview": "name: Checks\n\non:\n  push:\n    branches:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  composer-normalize:\n    "
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1296,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  build-lowest-version:\n    na"
  },
  {
    "path": ".github/workflows/static.yml",
    "chars": 1291,
    "preview": "name: Static analysis\n\non:\n  push:\n    branches:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  phpstan:\n    na"
  },
  {
    "path": ".gitignore",
    "chars": 105,
    "preview": "artifacts/\nvendor/\ncomposer.lock\nphpunit.xml\n.php-cs-fixer.php\n.php-cs-fixer.cache\n.phpunit.result.cache\n"
  },
  {
    "path": ".php-cs-fixer.dist.php",
    "chars": 739,
    "preview": "<?php\n\n$config = (new PhpCsFixer\\Config())\n    ->setRiskyAllowed(true)\n    ->setRules([\n        '@PHP71Migration:risky' "
  },
  {
    "path": "CHANGELOG.md",
    "chars": 2851,
    "preview": "# CHANGELOG\n\n\n## 2.3.0 - 2025-08-22\n\n### Added\n\n- PHP 8.5 support\n\n\n## 2.2.0 - 2025-03-27\n\n### Fixed\n\n- Revert \"Allow an"
  },
  {
    "path": "LICENSE",
    "chars": 1284,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Michael Dowling <mtdowling@gmail.com>\nCopyright (c) 2015 Graham Campbell <hell"
  },
  {
    "path": "Makefile",
    "chars": 189,
    "preview": "all: clean test\n\ntest:\n\tvendor/bin/phpunit\n\ncoverage:\n\tvendor/bin/phpunit --coverage-html=artifacts/coverage\n\nview-cover"
  },
  {
    "path": "README.md",
    "chars": 17638,
    "preview": "# Guzzle Promises\n\n[Promises/A+](https://promisesaplus.com/) implementation that handles promise\nchaining and resolution"
  },
  {
    "path": "composer.json",
    "chars": 1493,
    "preview": "{\n    \"name\": \"guzzlehttp/promises\",\n    \"description\": \"Guzzle promises library\",\n    \"keywords\": [\"promise\"],\n    \"lic"
  },
  {
    "path": "phpstan-baseline.neon",
    "chars": 207,
    "preview": "parameters:\n\tignoreErrors:\n\t\t-\n\t\t\tmessage: '#^Dead catch \\- GuzzleHttp\\\\Promise\\\\RejectionException is never thrown in t"
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 89,
    "preview": "includes:\n    - phpstan-baseline.neon\n\nparameters:\n    level: 5\n    paths:\n        - src\n"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 583,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    colors=\"true\"\n    beStrictAboutOutputDuringTests=\"true\"\n    beStrict"
  },
  {
    "path": "src/AggregateException.php",
    "chars": 413,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * Exception thrown when too many errors occur in th"
  },
  {
    "path": "src/CancellationException.php",
    "chars": 209,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * Exception that is set as the reason for a promise"
  },
  {
    "path": "src/Coroutine.php",
    "chars": 4163,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\nuse Generator;\nuse Throwable;\n\n/**\n * Creates a promise "
  },
  {
    "path": "src/Create.php",
    "chars": 2011,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\nfinal class Create\n{\n    /**\n     * Creates a promise fo"
  },
  {
    "path": "src/Each.php",
    "chars": 2664,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\nfinal class Each\n{\n    /**\n     * Given an iterator that"
  },
  {
    "path": "src/EachPromise.php",
    "chars": 7593,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * Represents a promise that iterates over many prom"
  },
  {
    "path": "src/FulfilledPromise.php",
    "chars": 2045,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * A promise that has been fulfilled.\n *\n * Thenning"
  },
  {
    "path": "src/Is.php",
    "chars": 941,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\nfinal class Is\n{\n    /**\n     * Returns true if a promis"
  },
  {
    "path": "src/Promise.php",
    "chars": 8961,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * Promises/A+ implementation that avoids recursion "
  },
  {
    "path": "src/PromiseInterface.php",
    "chars": 2823,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * A promise represents the eventual result of an as"
  },
  {
    "path": "src/PromisorInterface.php",
    "chars": 249,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * Interface used with classes that return a promise"
  },
  {
    "path": "src/RejectedPromise.php",
    "chars": 2268,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * A promise that has been rejected.\n *\n * Thenning "
  },
  {
    "path": "src/RejectionException.php",
    "chars": 1241,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * A special exception that is thrown when waiting o"
  },
  {
    "path": "src/TaskQueue.php",
    "chars": 2014,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\n/**\n * A task queue that executes tasks in a FIFO order."
  },
  {
    "path": "src/TaskQueueInterface.php",
    "chars": 450,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\ninterface TaskQueueInterface\n{\n    /**\n     * Returns tr"
  },
  {
    "path": "src/Utils.php",
    "chars": 8446,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise;\n\nfinal class Utils\n{\n    /**\n     * Get the global task q"
  },
  {
    "path": "tests/AggregateExceptionTest.php",
    "chars": 438,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\AggregateException;\nuse PHP"
  },
  {
    "path": "tests/CoroutineTest.php",
    "chars": 3686,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\Coroutine;\nuse GuzzleHttp\\P"
  },
  {
    "path": "tests/CreateTest.php",
    "chars": 1609,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/EachPromiseTest.php",
    "chars": 14847,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/EachTest.php",
    "chars": 908,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/FulfilledPromiseTest.php",
    "chars": 3075,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/IsTest.php",
    "chars": 1048,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/NotPromiseInstance.php",
    "chars": 1060,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\Promise;\nuse GuzzleHttp\\Pro"
  },
  {
    "path": "tests/PromiseTest.php",
    "chars": 22130,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/PropertyHelper.php",
    "chars": 620,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\n/**\n * A class to help get properties of an object"
  },
  {
    "path": "tests/RejectedPromiseTest.php",
    "chars": 4032,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise as P;\nuse GuzzleHttp\\Promis"
  },
  {
    "path": "tests/RejectionExceptionTest.php",
    "chars": 766,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\RejectionException;\nuse PHP"
  },
  {
    "path": "tests/TaskQueueTest.php",
    "chars": 907,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\TaskQueue;\nuse PHPUnit\\Fram"
  },
  {
    "path": "tests/Thennable.php",
    "chars": 477,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\Promise;\n\nclass Thennable\n{"
  },
  {
    "path": "tests/Thing1.php",
    "chars": 276,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nclass Thing1\n{\n    private $message;\n\n    public f"
  },
  {
    "path": "tests/Thing2.php",
    "chars": 214,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nclass Thing2 implements \\JsonSerializable\n{\n    #["
  },
  {
    "path": "tests/UtilsTest.php",
    "chars": 23970,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace GuzzleHttp\\Promise\\Tests;\n\nuse GuzzleHttp\\Promise\\AggregateException;\nuse Guz"
  },
  {
    "path": "vendor-bin/php-cs-fixer/composer.json",
    "chars": 155,
    "preview": "{\n    \"require\": {\n        \"php\": \"^7.4\",\n        \"friendsofphp/php-cs-fixer\": \"3.94.2\"\n    },\n    \"config\": {\n        \""
  },
  {
    "path": "vendor-bin/phpstan/composer.json",
    "chars": 199,
    "preview": "{\n    \"require\": {\n        \"php\": \"^8.2\",\n        \"phpstan/phpstan\": \"2.1.40\",\n        \"phpstan/phpstan-deprecation-rule"
  }
]

About this extraction

This page contains the full source code of the guzzle/promises GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (152.6 KB), approximately 39.5k tokens, and a symbol index with 297 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!