[Web] Update composer libraries

This commit is contained in:
FreddleSpl0it committed 2026-10-28 11:56:29 +01:00
1 parent 9cc8ac2908
commit bd8c784118
1297 files changed
+71585 -24460

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
# Agent Guidelines
## Code and tooling
- All code must remain compatible with PHP 7.4. Run PHPStan and PHP-CS-Fixer
only under a PHP 7.4.x runtime; never run either tool under any other PHP
major.minor version.
- Use fully qualified `#[\SensitiveParameter]` on concrete executable
parameters when their established role normally carries a secret,
credential-bearing aggregate, or confidential Guzzle-owned container, and
the active frame can throw or invoke throwing code.
- Repeat the attribute on every qualifying owned caller, callee, concrete trait
method, and closure parameter. Do not add it to interfaces, abstract-only
declarations, pure/no-realistic-throw helpers, assignment-only sites,
arbitrary generic payloads, or completed non-recoverable derivatives.
- For PHP 7.4 compatibility, put `#[\SensitiveParameter]` on its own line and
the parameter on the following line, expand the complete parameter list, and
never add a comma after the final parameter. Native trace redaction starts on
PHP 8.2 and does not redact logs, messages, properties, wire traffic, captured
variables, return values, or the separate backtrace `$this`/`object`.
- Always pass an explicit character list to `trim()`, `ltrim()`, and `rtrim()`;
never rely on the default characters.
- Handle `preg_*` engine failures: when the result is used as data, test for
`false` or `null` and throw a `\RuntimeException` including
`preg_last_error_msg()`; boolean validation guards must compare strictly, such
as `=== 1`, so an engine failure can only ever fail closed. Diagnostic
escaping is the narrow exception: use a deterministic bytewise fallback rather
than throwing, so it cannot obscure the original exception.
- Anchor validation patterns to the true end of input with the `D` modifier or
`\z`; a bare `$` accepts a trailing newline.
- Never embed raw control bytes in exception messages and other diagnostics;
escape or redact the offending value first.
- Helper classes that expose only public static methods are `final` and have a
private constructor.
- Resist native PHP serialization when a class holds live state (streams,
resources, handles, callbacks, credentials); plain data holders remain
serializable.
- To resist, `__serialize()` and `__unserialize()` both throw
`\LogicException(static::class.' should never be serialized')` and its
unserialized counterpart, via the `@internal` `NonSerializableTrait`.
- In general, numeric inputs should not accept non-finite floats. In situations
where they are accepted and we need to cast to a string, we should branch on
`\is_finite($value)`, using `(string) $value` for the finite case and
`\is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF')` otherwise.
- Never call `strtolower()`, `strtoupper()`, `strcasecmp()`, `stripos()`, or
other locale-sensitive case functions; fold ASCII case with
`strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')`
and its inverse instead.
- Changes in behavior need a `CHANGELOG.md` entry in the unreleased section of
the target branch and an `UPGRADING.md` note when the behavior differs between
major versions.
## Documentation
- Wrap markdown prose and PHPDoc text to 80 columns using greedy wrapping. Never
split a markdown link or an inline code span across a line break; a line that
cannot be broken may exceed the limit. Avoid em dashes.
- Keep PHPDoc and the corresponding `docs/` pages in sync: shared prose is
deliberately word-for-word identical, including boilerplate copied verbatim
between related functions, so apply the same edit to every copy. Only
formatting and linking may differ, such as a docs link becoming a PHPDoc
`@see` tag; the wording must never drift.
+168
View File
@@ -1,11 +1,176 @@
# CHANGELOG
## 3.1.0 - 2026-10-18
### Added
- Added `State` promise state checks as a replacement for `Is`
### Deprecated
- Deprecated `Is` because PHP 8.6 deprecates `is` as a class name
## 3.0.2 - 2026-08-24
### Added
- Added support for PHP 8.6
## 3.0.1 - 2026-08-05
### Changed
- Changed the default `TReason` of `FulfilledPromise` and `Create::promiseFor()` to `never`
- Changed the default `TValue` of `RejectedPromise` and `Create::rejectionFor()` to `never`
### Fixed
- Fixed `EachPromise` abandoning its aggregate when the pending window drains unsettled
- Fixed `EachPromise` admitting new work after its aggregate has settled
## 3.0.0 - 2026-07-20
### Added
- Added `concurrency` config support to `Utils::all()` and `Each::of()`
- Added generic PHPDoc annotations to promise APIs and collection callbacks
- Added recursive and `concurrency` config support to `Utils::settle()`
- Allowed promises to be resolved without passing a value
### Changed
- Changed `Utils::inspect()` to return actual rejection reasons
- Changed `Utils::inspect()` to prefer the settled state over late wait function exceptions
- Changed late rejection callbacks to follow rejected promises
- Reject native PHP serialization of in-flight runtime objects
- Made static helper classes non-instantiable
- Require iterable inputs for promise collection helpers and `EachPromise`
- Iterate `IteratorAggregate` inputs to collection helpers instead of treating them as a single value
- Improved recursive `Utils::all()` handling of dynamically-added settled values and raw values
### Removed
- Dropped support for PHP 7.2 and 7.3
## 2.5.1 - 2026-07-08
### Fixed
- Fixed recursive `Utils::all()` rejecting generator inputs
## 2.5.0 - 2026-06-02
### Deprecated
- Deprecated passing non-iterable inputs to promise collection helpers and `EachPromise`
## 2.4.1 - 2026-05-20
### Fixed
- Fixed cancelling settled coroutines when no current promise remains
## 2.4.0 - 2026-05-20
### Changed
- Empty `EachPromise` instances now resolve when the task queue runs without `wait()`
## 2.3.1 - 2026-05-19
### Fixed
- Fixed `Utils::inspect()` returning the internal reason array instead of the `AggregateException`
## 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
@@ -13,6 +178,7 @@
- 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
@@ -24,12 +190,14 @@
- 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
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+56 -502
View File
@@ -1,546 +1,100 @@
# 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.
`guzzlehttp/promises` is a small promise library used by Guzzle for asynchronous
operations. It implements promise chaining, synchronous waiting, cancellation,
and helpers for working with groups of 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)
Most application developers use this package through
[`guzzlehttp/guzzle`](https://github.com/guzzle/guzzle/blob/8.0/README.md) by
calling methods such as `requestAsync()`. Install this package directly when you
need promise composition without the full HTTP client.
## Installation
## Features
```bash
composer require guzzlehttp/promises
```
- [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()`.
## Version Guidance
| Version | Status | PHP Version |
|---------|--------------|--------------|
| 3.1 | Latest | >=7.4,<8.7 |
| 2.6 | Maintenance | >=7.2.5,<8.7 |
| 1.5 | End of Life | >=5.5,<8.3 |
## 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;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise->then(
// $onFulfilled
function ($value) {
echo 'The promise was fulfilled.';
echo 'Fulfilled: ' . $value;
},
// $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...)
echo 'Rejected: ' . $reason;
}
);
assert('waited' === $promise->wait());
$promise->resolve('done');
Utils::queue()->run();
```
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.
You can wait for a promise to complete synchronously:
```php
use GuzzleHttp\Promise\FulfilledPromise;
$promise = new FulfilledPromise('value');
// Fulfilled callbacks are immediately invoked.
$promise->then(function ($value) {
echo $value;
});
$value = $promise->wait();
```
A promise's `getState()` describes how it was settled, not the eventual
outcome: a promise that was resolved with another promise, directly or by
returning one from a `then()` handler, reports `fulfilled` while the inner
promise may still be pending, and `wait()` can still throw if the inner
promise rejects. Poll the state only on promises settled with plain values,
or call `wait()` first (see
[guzzle/promises#101](https://github.com/guzzle/promises/issues/101)).
### RejectedPromise
A rejected promise can be created to represent a promise that has been
rejected.
When using Guzzle HTTP requests, asynchronous methods return
`GuzzleHttp\Promise\PromiseInterface` instances:
```php
use GuzzleHttp\Promise\RejectedPromise;
$promise = new RejectedPromise('Error');
// Rejected callbacks are immediately invoked.
$promise->then(null, function ($reason) {
echo $reason;
});
$promise = $client->requestAsync('GET', 'https://example.com');
$response = $promise->wait();
```
## Documentation
## 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']);
```
*TODO*: Perhaps adding a `futureTick()` on each tick would be faster?
## 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 will be 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` |
- [Promise Quick Start](docs/promise-quick-start.md)
- [Promise API](docs/promise-api.md)
- [Promise Interoperability](docs/promise-interoperability.md)
- [Implementation Notes](docs/implementation-notes.md)
- [Upgrade Guide](UPGRADING.md)
- [Changelog](CHANGELOG.md)
## 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.
If you discover a security vulnerability within this package, please send an
email to security@tidelift.com. All security vulnerabilities will be promptly
addressed by the security maintainers listed in the
[Security Policy](https://github.com/guzzle/promises/security/policy). Please do
not disclose security-related issues publicly until a fix has been announced.
## License
Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
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)
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)
+279
View File
@@ -0,0 +1,279 @@
Guzzle Promises Upgrade Guide
=============================
2.x to 3.0
----------
Guzzle Promises 3.0 is a major release that raises the minimum PHP version,
updates promise and collection helper signatures, adds generic PHPDoc types for
static analyzers, tightens collection helper inputs, improves recursive
collection behavior, and clarifies rejection inspection and late rejection
callback behavior.
#### PHP Version and Dependencies
Guzzle Promises 3.0 requires PHP `^7.4 || ^8.0`. Guzzle Promises 2.x supported
PHP `^7.2.5 || ^8.0`.
If your application still supports PHP 7.2 or 7.3, continue using Guzzle
Promises 2.x until your minimum PHP version is raised.
Guzzle Promises 3.0 has no runtime package dependencies beyond PHP. It removes
the 2.x runtime dependency on `symfony/deprecation-contracts`; require that
package directly if your application uses it.
#### Optional Promise Resolution Values
`PromiseInterface::resolve()` now accepts an optional value. Calling `resolve()`
without an argument fulfills the promise with `null`.
Custom implementations of `PromiseInterface`, and subclasses that override
`resolve()` on `Promise`, `FulfilledPromise`, or `RejectedPromise`, must update
their method signature from `resolve($value): void` to
`resolve($value = null): void`.
#### Generic PHPDoc Types
`PromiseInterface`, `PromisorInterface`, and the built-in promise classes now
include generic PHPDoc annotations for static analysis tools. The first template
type represents the fulfillment value and the second represents the rejection
reason. This is a static-analysis-only change and does not alter runtime
behavior, but projects with stricter static analysis may see new or different
diagnostics:
```php
use GuzzleHttp\Promise\PromiseInterface;
/** @var PromiseInterface<string, \Throwable> */
$promise = $factory->createPromise();
$value = $promise->wait();
```
Code that uses unparameterized promise types continues to work and is treated as
`PromiseInterface<mixed, mixed>`. If your project implements promise interfaces,
extends promise classes, or has stricter static analysis, you may need to update
your PHPDoc annotations to include the generic value and reason types. The
expanded PHPDoc also preserves promise-chain fulfillment and rejection types
more precisely through `then()` and `otherwise()`, and documents collection
callbacks with value or reason, key, and aggregate-promise arguments so
callbacks may declare only the arguments they use.
#### Collection Helper Inputs
Promise collection helpers now require iterable inputs. Passing a single promise
or scalar value directly now throws a `TypeError`.
Wrap single promises or values in an array before passing them to
`Create::iterFor()`, `Each::of()`, `Each::ofLimit()`, `Each::ofLimitAll()`,
`EachPromise`, or the `Utils` collection helpers.
```php
use GuzzleHttp\Promise\Each;
// 2.x
$promise = Each::ofLimit($singlePromise, 2);
// 3.0
$promise = Each::ofLimit([$singlePromise], 2);
```
`IteratorAggregate` inputs are now iterated via `getIterator()`. In 2.x an
aggregate was treated as a single value and produced one result; in 3.0 its
entries are consumed individually.
```php
use GuzzleHttp\Promise\Utils;
$aggregate = new \ArrayObject([$promiseA, $promiseB]);
// 2.x: one result — the ArrayObject itself
// 3.0: two results — the fulfillment values of $promiseA and $promiseB
$promise = Utils::all($aggregate);
```
#### Collection Helper Signatures
`Utils::all()`, `Utils::settle()`, and `Each::of()` now accept trailing optional
arguments. Direct calls using the 2.x argument lists continue to work, and the
affected helper classes are final so subclass signatures do not need to change.
Code that mirrors or reflects exact helper signatures may need to be updated.
Pass the recursive flag before the config array when using `Utils::all()` or
`Utils::settle()`:
```php
use GuzzleHttp\Promise\Utils;
$promise = Utils::all($promises, false, ['concurrency' => 5]);
$promise = Utils::settle($promises, false, ['concurrency' => 5]);
```
Only `concurrency` is honored by these helper config arrays. Callback config
keys such as `fulfilled` and `rejected` are ignored; pass callbacks to
`Each::of()` directly or use `EachPromise`.
#### Recursive Collection Helpers
Existing `Utils::all($promises, true)` calls may return different results in
3.0. Recursive mode now detects dynamically-added settled promises and raw
values. In 2.x, recursive mode only checked for pending promises.
If you previously worked around the lack of recursive `Utils::settle()` support,
you can replace that workaround with the new `$recursive` argument:
```php
use GuzzleHttp\Promise\Utils;
$promise = Utils::settle($promises, true);
```
When `$recursive` is true, collection helpers continue taking passes over the
collection until no new entries are found and no visible promises remain
pending. This is intended for rewindable mutable collections such as
`ArrayIterator`. Generators are safe to pass, but they cannot be traversed
again once consumed, so recursive mode degrades to a single pass; use a
rewindable mutable collection when recursion needs to observe added values.
#### Promise Inspection
`Utils::inspect()` and `Utils::inspectAll()` now return the actual rejection
reason delivered to rejection callbacks. They no longer unwrap
`RejectionException` instances to their inner reason.
For example, a promise rejected with a `RejectionException` now inspects with
that exception as the reason:
```php
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\RejectionException;
use GuzzleHttp\Promise\Utils;
$reason = new RejectionException('reason');
$result = Utils::inspect(new RejectedPromise($reason));
assert($result['reason'] === $reason);
```
Cancelled promises now inspect with a `CancellationException` reason. If you
need the string reason from a `RejectionException` or subclass, call
`getReason()` on the exception.
`Utils::inspect()` still reports wait-function failures as rejection reasons
when the wait function does not settle the promise. If the wait function
settles the promise and then throws, `inspect()` reports the settled
fulfillment or rejection state; direct `Promise::wait()` calls continue to
throw that late exception.
#### Late Rejection Callbacks
Rejection callbacks registered after a promise was resolved with a rejected
promise are now invoked with the nested rejection reason.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise->resolve(new RejectedPromise('reason'));
$promise->then(null, function ($reason): void {
assert($reason === 'reason');
});
Utils::queue()->run();
```
#### Non-instantiable Helper Classes
Static helper classes such as `Create`, `Each`, `Is`, and `Utils` now have
private constructors. Replace any accidental instantiation with static method
calls.
#### Native PHP Serialization of Runtime Objects
`Promise`, `TaskQueue`, `EachPromise`, and `Coroutine` no longer support native
PHP `serialize()` or `unserialize()`. Persist application values instead of
promise runtime state.
1.x to 2.0
----------
Guzzle Promises 2.0 is a major release that removes deprecated APIs, raises the
minimum PHP version, and adds PHP 7 parameter and return types. Applications that
only use the object-oriented API should usually need small changes. Applications
that call helper functions, implement package interfaces, extend package classes,
or pass invalid argument types need closer review.
#### PHP Version and Dependencies
Guzzle Promises 2.0 requires PHP `^7.2.5 || ^8.0`. Guzzle Promises 1.x
supported PHP `>=5.5`.
#### PHP 7 Type Hints and Return Types
Type hints and return types were added wherever possible. Please make sure:
- You pass values of the documented type when calling methods and functions.
- Classes that implement `PromiseInterface`, `PromisorInterface`, or
`TaskQueueInterface` update method signatures to remain compatible.
- Classes that extend Guzzle Promises classes update any overridden method
signatures to remain compatible.
- Code that expected package-specific exceptions for invalid argument types may
now receive PHP `TypeError` exceptions instead.
#### Soft-Final Classes
All previously non-final non-exception classes are now final or annotated with
`@final`. If your code extends one of these classes, replace inheritance with
composition or implement the relevant interface directly.
#### Removed Function API
The static API was introduced in 1.4.0 to mitigate problems with functions
conflicting between global and local copies of the package. The function API was
removed in 2.0.0, along with the Composer `files` autoload entry that loaded
`src/functions_include.php`.
Replace namespaced function calls with the corresponding static methods in the
`GuzzleHttp\Promise` namespace:
```php
// Before:
use function GuzzleHttp\Promise\promise_for;
$promise = promise_for('value');
// After:
use GuzzleHttp\Promise\Create;
$promise = Create::promiseFor('value');
```
| 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` |
For the full 2.0 diff, see
https://github.com/guzzle/promises/compare/1.5.3...2.0.0.
+17 -14
View File
@@ -1,8 +1,10 @@
{
"name": "guzzlehttp/promises",
"description": "Guzzle promises library",
"keywords": ["promise"],
"license": "MIT",
"keywords": [
"promise"
],
"authors": [
{
"name": "Graham Campbell",
@@ -26,33 +28,34 @@
}
],
"require": {
"php": ">=5.5"
"php": "^7.4 || ^8.0",
"symfony/deprecation-contracts": "^2.5 || ^3.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4 || ^5.1"
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^9.6.34"
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": ["src/functions_include.php"]
}
},
"autoload-dev": {
"psr-4": {
"GuzzleHttp\\Promise\\Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/simple-phpunit",
"test-ci": "vendor/bin/simple-phpunit --coverage-text"
},
"extra": {
"branch-alias": {
"dev-master": "1.5-dev"
}
},
"config": {
"allow-plugins": {
"bamarni/composer-bin-plugin": true
},
"preferred-install": "dist",
"sort-packages": true
},
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
}
}
@@ -0,0 +1,72 @@
# Implementation Notes
This document describes implementation details that explain observable behavior
in `guzzlehttp/promises`, especially queue-based callback execution, iterative
resolution, and why `Promise` also acts as the deferred value. Application code
usually only needs the [Promise Quick Start](promise-quick-start.md) and
[Promise API](promise-api.md).
## Iterative Resolution and Chaining
Promises are resolved iteratively by moving pending handlers between promises.
This keeps stack size constant even for very long `then()` chains.
```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.
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
takes ownership of each child promise's handlers and delivers values down the
chain without 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 pending handlers receive 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. That separation prevents consumers from modifying the value
that will eventually be delivered.
Iterative resolution requires one promise to move handlers from another promise.
To do that without making handlers publicly mutable, `Promise` is also the
deferred value. Promises of the same class can modify each other's private
state, including handler ownership. This means a consumer that receives a
`Promise` can also resolve or reject it, but it keeps chaining efficient and
stack safe.
```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');
GuzzleHttp\Promise\Utils::queue()->run();
// Prints "foo"
```
## Related
- [Quick Start](promise-quick-start.md)
- [Promise API](promise-api.md)
- [Promise Interoperability](promise-interoperability.md)
@@ -0,0 +1,177 @@
# Promise API
This reference summarizes the public API provided by `guzzlehttp/promises`.
Promise APIs are documented for static analysis as
`PromiseInterface<TValue, TReason>`. `TValue` is the fulfillment value type and
`TReason` is the rejection reason type. This typing is PHPDoc-only and does not
change runtime behavior.
Callbacks registered with `then()` are queued. They are invoked when the global
task queue runs, when a returned promise is waited on, or when the queue is
drained by the default shutdown handler.
## PromiseInterface and Promise
`PromiseInterface` defines the common promise contract and the states
`pending`, `fulfilled`, and `rejected`.
When creating a `Promise`, you can provide an optional `$waitFn` and
`$cancelFn`. `$waitFn` receives a boolean argument and is expected to resolve or
reject the promise. `$cancelFn` receives no arguments and is invoked when
`cancel()` is called.
```php
use GuzzleHttp\Promise\Promise;
$promise = new Promise(
function (bool $recursive) use (&$promise) {
$promise->resolve('waited');
},
function () {
// Cancel the underlying operation, such as closing a socket.
}
);
assert('waited' === $promise->wait());
```
A promise has the following methods:
- `then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface` appends fulfillment and rejection handlers and returns a new promise resolving to the return value of the called handler. If a handler is omitted, the original fulfillment value or rejection reason is forwarded.
- `otherwise(callable $onRejected) : PromiseInterface` appends a rejection handler and returns a new promise resolving to the callback result if called, or to the original fulfillment value if the promise is fulfilled.
- `wait(bool $unwrap = true) : mixed` synchronously waits on the promise. When `$unwrap` is `true`, fulfilled values are returned and rejected reasons are thrown. When `$unwrap` is `false`, the promise is settled without returning or throwing its result.
- `cancel() : void` attempts to cancel the promise and dependent promises.
- `getState() : string` returns `pending`, `fulfilled`, or `rejected`.
- `resolve($value = null) : void` fulfills the promise with `$value`, or with `null` if no value is given.
- `reject($reason) : void` rejects the promise with `$reason`.
## Settled Promises
`FulfilledPromise` represents an already fulfilled promise. Fulfillment
callbacks are still queued and run when the task queue runs or the returned
promise is waited on.
```php
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\Utils;
$promise = new FulfilledPromise('value');
$promise->then(function ($value) {
echo $value;
});
Utils::queue()->run();
```
`RejectedPromise` represents an already rejected promise. Rejection callbacks
are also queued and run when the queue is drained or the returned promise is
waited on.
```php
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\Utils;
$promise = new RejectedPromise('Error');
$promise->then(null, function ($reason) {
echo $reason;
});
Utils::queue()->run();
```
## Utils
`GuzzleHttp\Promise\Utils` provides helpers for inspecting, aggregating, and
queuing promise work.
- `Utils::queue(?TaskQueueInterface $assign = null) : TaskQueueInterface` returns the global task queue, or assigns a replacement queue when `$assign` is provided.
- `Utils::task(callable $task) : PromiseInterface` adds a task to the global queue and returns a promise that is fulfilled or rejected with the task result.
- `Utils::inspect(PromiseInterface $promise) : array` waits for one promise to settle and returns an inspection array with `state` and either `value` or `reason`.
- `Utils::inspectAll(iterable $promises) : array` inspects each promise and returns inspection arrays keyed like the input iterable.
- `Utils::unwrap(iterable $promises) : array` waits on all promises and returns fulfilled values, throwing if any promise rejects.
- `Utils::all(iterable $promises, bool $recursive = false, array $config = []) : PromiseInterface` returns a promise fulfilled with all values, or rejected when any input rejects.
- `Utils::settle(iterable $promises, bool $recursive = false, array $config = []) : PromiseInterface` returns a promise fulfilled with inspection arrays after all inputs settle.
- `Utils::some(int $count, iterable $promises) : PromiseInterface` fulfills with the values of the first `$count` promises to fulfill, in the order they appear in the input, or rejects with `AggregateException` if too few fulfill.
- `Utils::any(iterable $promises) : PromiseInterface` fulfills with the first fulfilled value, or rejects with `AggregateException` if none fulfill.
`Utils::all()` and `Utils::settle()` accept `['concurrency' => 5]` or
`['concurrency' => callable]` for lazy iterables. This limits how many items are
pulled from the iterable at one time; it does not throttle promises that have
already been created or started.
```php
use GuzzleHttp\Promise\Utils;
$promise = Utils::all($promises, false, ['concurrency' => 5]);
```
## Create
`GuzzleHttp\Promise\Create` provides factories used by the promise
implementation and by callers that need to normalize values.
- `Create::promiseFor($value) : PromiseInterface` returns `$value` when it is already a Guzzle promise, wraps foreign thenables in a Guzzle promise, or returns a fulfilled promise for plain values.
- `Create::rejectionFor($reason) : PromiseInterface` returns `$reason` when it is already a promise, or returns a rejected promise for plain reasons.
- `Create::exceptionFor($reason) : Throwable` returns throwable reasons as-is, or wraps non-throwable reasons in `RejectionException`.
- `Create::iterFor(iterable $value) : Iterator` returns an iterator for arrays, iterators, iterator aggregates, and traversables.
## State
`GuzzleHttp\Promise\State` provides readable state checks:
- `State::isPending(PromiseInterface $promise) : bool`
- `State::isSettled(PromiseInterface $promise) : bool`
- `State::isFulfilled(PromiseInterface $promise) : bool`
- `State::isRejected(PromiseInterface $promise) : bool`
`GuzzleHttp\Promise\Is` is deprecated because PHP 8.6 deprecates `is` as a class
name, and will be removed in Guzzle Promises 4.0. Replace `Is::pending()`,
`Is::settled()`, `Is::fulfilled()`, and `Is::rejected()` with the matching
`State` methods.
## Each and EachPromise
`Each::of()` consumes an iterable of promises or values and invokes callbacks as
items settle. Fulfillment callbacks receive the fulfilled value, iterable key,
and aggregate promise. Rejection callbacks receive the rejection reason,
iterable key, and aggregate promise. Callback return values are ignored.
```php
use GuzzleHttp\Promise\Each;
$promise = Each::of($promises, $onFulfilled, $onRejected, ['concurrency' => 5]);
```
- `Each::of(iterable $iterable, ?callable $onFulfilled = null, ?callable $onRejected = null, array $config = []) : PromiseInterface` consumes the iterable and optionally limits lazy iteration with `concurrency`.
- `Each::ofLimit(iterable $iterable, $concurrency, ?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface` is a convenience wrapper for `Each::of()` with a concurrency limit.
- `Each::ofLimitAll(iterable $iterable, $concurrency, ?callable $onFulfilled = null) : PromiseInterface` is like `ofLimit()`, but rejects the aggregate promise on the first rejection.
- `EachPromise` is the configurable class behind `Each`; use it directly when you need `fulfilled`, `rejected`, and `concurrency` keys in one configuration array.
The concurrency options limit lazy promise creation. For HTTP request
concurrency, use `GuzzleHttp\Pool` from `guzzlehttp/guzzle`.
## Coroutine
`Coroutine::of(callable $generatorFn) : Coroutine` creates a promise resolved by
a generator that yields values or promises. The generator resumes with each
fulfilled value, and rejections are thrown into the generator.
## Task Queue
`TaskQueueInterface` exposes `isEmpty()`, `add(callable $task)`, and `run()`.
`TaskQueue` executes queued tasks in FIFO order. The default queue runs at
process shutdown unless disabled, and `wait()` drains the queue while resolving
the promise being waited on.
## Exceptions
- `RejectionException` is thrown by `wait()` when a rejected promise has a non-throwable reason. The original reason is available through `getReason()`.
- `AggregateException` extends `RejectionException` and is used by `Utils::some()` and `Utils::any()` when too few promises fulfill.
- `CancellationException` extends `RejectionException` and is used as the rejection reason for cancelled promises.
## Related
- [Quick Start](promise-quick-start.md)
- [Promise Interoperability](promise-interoperability.md)
- [Implementation Notes](implementation-notes.md)
- [Upgrade Guide](../UPGRADING.md)
@@ -0,0 +1,78 @@
# Promise Interoperability
This guide explains how Guzzle promises interact with foreign promise
implementations. A foreign promise is any object with a `then` method, such as a
[React promise](https://github.com/reactphp/promise). When a foreign promise is
returned from a `then` callback, Guzzle forwards resolution to that promise.
## Foreign Promises
Capture the promise returned from `then()`. That chained promise is the Guzzle
promise that follows the foreign promise's eventual result.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
$deferred = new React\Promise\Deferred();
$reactPromise = $deferred->promise();
$guzzlePromise = new Promise();
$chained = $guzzlePromise->then(function ($value) use ($reactPromise) {
// Use the Guzzle value, then continue with the React promise.
return $reactPromise;
});
$chained->then(function ($value) {
echo $value;
});
$guzzlePromise->resolve('start');
Utils::queue()->run();
$deferred->resolve('done');
Utils::queue()->run();
```
Forwarding a foreign promise does not make Guzzle able to synchronously wait on
or cancel the foreign operation. The chained Guzzle promise settles only after
the foreign implementation invokes the callbacks registered through `then()`.
If the foreign promise has no compatible `wait()` or `cancel()` behavior, Guzzle
cannot invent that behavior.
Use `Create::promiseFor($foreignPromise)` to shadow a foreign thenable as a
Guzzle promise. If the foreign object exposes `wait()` or `cancel()` methods,
the wrapper uses them. Otherwise, use the foreign implementation's event loop or
completion mechanism and drain Guzzle's task queue when callbacks need to run.
## Event Loop Integration
Guzzle promises use a task queue to keep stack size constant and to run promise
callbacks asynchronously. When waiting on promises synchronously, the task queue
is automatically run while resolving the blocking promise and forwarded Guzzle
promises.
When using promises asynchronously in an event loop, run the task queue on loop
ticks. If you do not run the task queue, Guzzle promise callbacks may remain
queued.
```php
$queue = GuzzleHttp\Promise\Utils::queue();
$queue->run();
```
For example, you could use Guzzle promises with React using a short periodic
timer. Avoid zero-interval timers because they may keep the loop busy even when
there is no promise work to run.
```php
$queue = GuzzleHttp\Promise\Utils::queue();
$loop = React\EventLoop\Factory::create();
$loop->addPeriodicTimer(0.01, [$queue, 'run']);
```
## Related
- [Quick Start](promise-quick-start.md)
- [Promise API](promise-api.md)
- [Implementation Notes](implementation-notes.md)
@@ -0,0 +1,319 @@
# Promise Quick Start
This guide covers the common promise operations needed when using
`guzzlehttp/promises` directly: registering callbacks, resolving or rejecting
promises, waiting synchronously, composing chains, and using generator-based
flows. For the full public surface, see the [Promise API](promise-api.md).
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 the eventual value or the reason why the
promise cannot be fulfilled.
Guzzle promise callbacks are queued. They run when the task queue is drained,
for example by `Utils::queue()->run()`, by waiting on a returned promise, or by
the default shutdown handler at the end of the PHP process. Examples that show
callback output drain the queue explicitly.
## 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*. Callbacks registered with `then` are invoked
only once and in the order in which they were added when the queue is drained.
## Resolving a Promise
Promises are fulfilled using the `resolve($value = null)` method. Calling
`resolve()` without an argument fulfills the promise with `null`. Resolving a
promise with any value other than a `GuzzleHttp\Promise\RejectedPromise` queues
the `$onFulfilled` callbacks. Resolving with a rejected promise rejects the
promise and queues the `$onRejected` callbacks.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise
->then(function ($value) {
return 'Hello, ' . $value;
})
->then(function ($value) {
echo $value;
});
$promise->resolve('reader.');
Utils::queue()->run();
// Outputs "Hello, reader."
```
## Promise Forwarding
Promises can be chained one after the other. Each `then` call returns a new
promise. The return value of a callback is forwarded to the next promise in the
chain. Returning a promise from a callback makes the next promise wait for that
returned promise to settle.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$nextPromise = new Promise();
$promise
->then(function ($value) use ($nextPromise) {
echo $value;
return $nextPromise;
})
->then(function ($value) {
echo $value;
});
$promise->resolve('A');
Utils::queue()->run();
// Outputs "A"
$nextPromise->resolve('B');
Utils::queue()->run();
// Outputs "B"
```
## Promise Rejection
When a promise is rejected, the `$onRejected` callbacks are invoked with the
rejection reason when the queue is drained.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise->then(null, function ($reason) {
echo $reason;
});
$promise->reject('Error!');
Utils::queue()->run();
// Outputs "Error!"
```
## Rejection Forwarding
If an exception is thrown in an `$onRejected` callback, subsequent
`$onRejected` callbacks receive the thrown exception as the reason.
```php
use GuzzleHttp\Promise\Promise;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise->then(null, function ($reason) {
throw new Exception($reason);
})->then(null, function ($reason) {
assert($reason->getMessage() === 'Error!');
});
$promise->reject('Error!');
Utils::queue()->run();
```
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;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise->then(null, function ($reason) {
return new RejectedPromise($reason);
})->then(null, function ($reason) {
assert($reason === 'Error!');
});
$promise->reject('Error!');
Utils::queue()->run();
```
If an exception is not thrown in an `$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;
use GuzzleHttp\Promise\Utils;
$promise = new Promise();
$promise
->then(null, function ($reason) {
return "It's ok";
})
->then(function ($value) {
assert($value === "It's ok");
});
$promise->reject('Error!');
Utils::queue()->run();
```
## 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 complete the promise. The wait function receives a boolean
argument and is expected to resolve or reject the promise. If the wait function
does not settle the promise, an exception is thrown.
```php
use GuzzleHttp\Promise\Promise;
$promise = new Promise(function (bool $recursive) use (&$promise) {
$promise->resolve('foo');
});
echo $promise->wait();
// Outputs "foo"
```
If a throwable is encountered while invoking the wait function of a promise,
the promise is rejected with the throwable and the throwable is thrown.
```php
$promise = new Promise(function (bool $recursive) 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 (bool $recursive) { die('this is not called!'); });
$promise->resolve('foo');
echo $promise->wait();
// Outputs "foo"
```
Calling `wait` on a promise that has been rejected will throw. If the rejection
reason is an instance of `\Throwable`, the reason is thrown. Otherwise, a
`GuzzleHttp\Promise\RejectionException` is thrown and the reason can be
obtained by calling `getReason()` on 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 reason: foo'
## Unwrapping a Promise
When synchronously waiting on a promise, you are joining the state of the
promise into the current execution: the fulfilled value is returned, or the
rejection reason is thrown. This is called "unwrapping" the promise. Waiting on
a promise unwraps by default.
You can force a promise to resolve and *not* unwrap its state by passing
`false` to `wait()`:
```php
$promise = new Promise();
$promise->reject('foo');
// This does not throw. It only ensures the promise has been resolved.
$promise->wait(false);
```
When unwrapping a promise, the resolved value of the promise will be waited on
until the unwrapped value is not a promise. This means that if promise A is
resolved with promise B, unwrapping promise A returns the value delivered to
promise B.
When you do not unwrap the promise, no value is returned.
## Inspecting a Promise
`Utils::inspect($promise)` waits for a promise to settle and returns an array
describing its final state. For rejected promises, the `reason` entry is the
actual rejection reason delivered to rejection callbacks.
This means `RejectionException` and subclasses are not unwrapped by
`inspect()`. For example, cancelled promises inspect with a
`CancellationException` reason.
## Generator-Based Async
`Coroutine::of()` creates a promise from a generator that yields values or
promises. The generator resumes each time the yielded value settles, which can
make sequential asynchronous flows easier to read.
```php
use GuzzleHttp\Promise\Coroutine;
use GuzzleHttp\Promise\FulfilledPromise;
$promise = Coroutine::of(function () {
$first = yield new FulfilledPromise('A');
$second = yield new FulfilledPromise($first . 'B');
yield $second . 'C';
});
echo $promise->wait();
// Outputs "ABC"
```
If a yielded promise rejects, the rejection is thrown into the generator. Catch
it inside the generator to recover, or let it reject the coroutine promise.
## Cancellation
You can cancel a promise that has not yet been fulfilled using `cancel()`. When
creating a promise, you can provide an optional cancel function that cancels the
underlying operation, such as closing a socket or aborting a query.
```php
use GuzzleHttp\Promise\Promise;
$promise = new Promise(null, function () {
// Cancel the underlying operation.
});
$promise->cancel();
```
Cancellation rejects the promise with a `CancellationException` unless the
cancel function settles the promise first.
## Related
- [Promise API](promise-api.md)
- [Promise Interoperability](promise-interoperability.md)
- [Implementation Notes](implementation-notes.md)
- [Upgrade Guide](../UPGRADING.md)
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -7,7 +9,7 @@ namespace GuzzleHttp\Promise;
*/
class AggregateException extends RejectionException
{
public function __construct($msg, array $reasons)
public function __construct(string $msg, array $reasons)
{
parent::__construct(
$reasons,
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
+67 -41
View File
@@ -1,10 +1,10 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
use Exception;
use Generator;
use Throwable;
/**
* Creates a promise that is resolved using a generator that yields values or
@@ -27,7 +27,7 @@ use Throwable;
* $value = (yield createPromise('a'));
* try {
* $value = (yield createPromise($value . 'b'));
* } catch (\Exception $e) {
* } catch (\Throwable $e) {
* // The promise was rejected.
* }
* yield $value . 'c';
@@ -36,42 +36,46 @@ use Throwable;
* // Outputs "abc"
* $promise->then(function ($v) { echo $v; });
*
* @param callable $generatorFn Generator function to wrap into a promise.
* @template TValue = mixed
* @template TReason = mixed
*
* @return Promise
* @implements PromiseInterface<TValue, TReason>
*
* @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
* @see https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
*/
final class Coroutine implements PromiseInterface
{
/**
* @var PromiseInterface|null
*/
private $currentPromise;
use NonSerializableTrait;
/**
* @var Generator
* @var PromiseInterface<mixed, mixed>|null
*/
private $generator;
private ?PromiseInterface $currentPromise = null;
/**
* @var Promise
* @var Generator<mixed, mixed, mixed, mixed>
*/
private $result;
private Generator $generator;
/**
* @var Promise<TValue, TReason>
*/
private PromiseInterface $result;
/**
* @param callable(): Generator<mixed, mixed, mixed, mixed> $generatorFn
*/
public function __construct(callable $generatorFn)
{
$this->generator = $generatorFn();
$this->result = new Promise(function () {
$this->result = new Promise(function (): void {
while (isset($this->currentPromise)) {
$this->currentPromise->wait();
}
});
try {
$this->nextCoroutine($this->generator->current());
} catch (\Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
} catch (\Throwable $throwable) {
$this->result->reject($throwable);
}
}
@@ -79,52 +83,76 @@ final class Coroutine implements PromiseInterface
/**
* Create a new coroutine.
*
* @return self
* @param callable(): Generator<mixed, mixed, mixed, mixed> $generatorFn
*
* @return self<mixed, mixed>
*/
public static function of(callable $generatorFn)
public static function of(callable $generatorFn): self
{
return new self($generatorFn);
}
/**
* @template TFulfilledValue = never
* @template TFulfilledReason = never
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
* @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)>
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
return $this->result->then($onFulfilled, $onRejected);
}
public function otherwise(callable $onRejected)
/**
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>) $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<TValue|TRejectedValue, TRejectedReason|\Throwable>
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->result->otherwise($onRejected);
}
public function wait($unwrap = true)
public function wait(bool $unwrap = true)
{
return $this->result->wait($unwrap);
}
public function getState()
public function getState(): string
{
return $this->result->getState();
}
public function resolve($value)
public function resolve($value = null): void
{
$this->result->resolve($value);
}
public function reject($reason)
public function reject($reason): void
{
$this->result->reject($reason);
}
public function cancel()
public function cancel(): void
{
$this->currentPromise->cancel();
if (isset($this->currentPromise)) {
$this->currentPromise->cancel();
}
$this->result->cancel();
}
private function nextCoroutine($yielded)
private function nextCoroutine($yielded): void
{
$this->currentPromise = Create::promiseFor($yielded)
->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
@@ -133,9 +161,10 @@ final class Coroutine implements PromiseInterface
/**
* @internal
*/
public function _handleSuccess($value)
public function _handleSuccess($value): void
{
unset($this->currentPromise);
$this->currentPromise = null;
try {
$next = $this->generator->send($value);
if ($this->generator->valid()) {
@@ -143,9 +172,7 @@ final class Coroutine implements PromiseInterface
} else {
$this->result->resolve($value);
}
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
} catch (\Throwable $throwable) {
$this->result->reject($throwable);
}
}
@@ -153,16 +180,15 @@ final class Coroutine implements PromiseInterface
/**
* @internal
*/
public function _handleFailure($reason)
public function _handleFailure($reason): void
{
unset($this->currentPromise);
$this->currentPromise = null;
try {
$nextYield = $this->generator->throw(Create::exceptionFor($reason));
// The throw was caught, so keep iterating on the coroutine
$this->nextCoroutine($nextYield);
} catch (Exception $exception) {
$this->result->reject($exception);
} catch (Throwable $throwable) {
} catch (\Throwable $throwable) {
$this->result->reject($throwable);
}
}
+44 -19
View File
@@ -1,17 +1,28 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
final class Create
{
private function __construct()
{
}
/**
* Creates a promise for a value if the value is not a promise.
* Returns `$value` when it is already a Guzzle promise, wraps foreign
* thenables in a Guzzle promise, or returns a fulfilled promise for plain
* values.
*
* @param mixed $value Promise or value.
* @template TValue
* @template TPromise of PromiseInterface<mixed, mixed> = PromiseInterface<mixed, mixed>
*
* @return PromiseInterface
* @param TValue|TPromise $value Promise or value.
*
* @return ($value is PromiseInterface ? TPromise : FulfilledPromise<TValue, never>)
*/
public static function promiseFor($value)
public static function promiseFor($value): PromiseInterface
{
if ($value instanceof PromiseInterface) {
return $value;
@@ -23,6 +34,7 @@ final class Create
$cfn = method_exists($value, 'cancel') ? [$value, 'cancel'] : null;
$promise = new Promise($wfn, $cfn);
$value->then([$promise, 'resolve'], [$promise, 'reject']);
return $promise;
}
@@ -30,14 +42,18 @@ final class Create
}
/**
* 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.
* Returns `$reason` when it is already a promise, or returns a rejected
* promise for plain reasons.
*
* @param mixed $reason Promise or reason.
* @template TReason
* @template TValue = never
* @template TPromise of PromiseInterface<mixed, mixed> = PromiseInterface<mixed, mixed>
*
* @return PromiseInterface
* @param TReason|TPromise $reason Promise or reason.
*
* @return ($reason is PromiseInterface ? TPromise : RejectedPromise<TValue, TReason>)
*/
public static function rejectionFor($reason)
public static function rejectionFor($reason): PromiseInterface
{
if ($reason instanceof PromiseInterface) {
return $reason;
@@ -47,15 +63,16 @@ final class Create
}
/**
* Create an exception for a rejected promise value.
* Returns throwable reasons as-is, or wraps non-throwable reasons in
* `RejectionException`.
*
* @param mixed $reason
* @template TReason
*
* @return \Exception|\Throwable
* @param TReason $reason
*/
public static function exceptionFor($reason)
public static function exceptionFor($reason): \Throwable
{
if ($reason instanceof \Exception || $reason instanceof \Throwable) {
if ($reason instanceof \Throwable) {
return $reason;
}
@@ -63,13 +80,17 @@ final class Create
}
/**
* Returns an iterator for the given value.
* Returns an iterator for arrays, iterators, iterator aggregates, and
* traversables.
*
* @param mixed $value
* @template TKey of array-key
* @template TValue
*
* @return \Iterator
* @param iterable<TKey, TValue> $value
*
* @return \Iterator<TKey, TValue>
*/
public static function iterFor($value)
public static function iterFor(iterable $value): \Iterator
{
if ($value instanceof \Iterator) {
return $value;
@@ -79,6 +100,10 @@ final class Create
return new \ArrayIterator($value);
}
return new \ArrayIterator([$value]);
if ($value instanceof \IteratorAggregate) {
return self::iterFor($value->getIterator());
}
return new \IteratorIterator($value);
}
}
+71 -43
View File
@@ -1,37 +1,63 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
final class Each
{
private function __construct()
{
}
/**
* 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
* $onFulfilled is a function that accepts the fulfilled value, iterable
* key, 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
* $onRejected is a function that accepts the rejection reason, iterable
* key, 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.
* @param callable $onFulfilled
* @param callable $onRejected
* The config array accepts a concurrency option matching {@see ofLimit}.
* Other config keys are ignored by this wrapper.
*
* @return PromiseInterface
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $iterable Iterator or array to iterate over.
* @param (callable(TValue, TKey, PromiseInterface<mixed, mixed>): mixed)|null $onFulfilled
* @param (callable(TReason, TKey, PromiseInterface<mixed, mixed>): mixed)|null $onRejected
* @param array{concurrency?: int|(callable(int): int)} $config Configuration options.
*
* @return PromiseInterface<mixed, mixed>
*/
public static function of(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected
]))->promise();
iterable $iterable,
?callable $onFulfilled = null,
?callable $onRejected = null,
array $config = []
): PromiseInterface {
$eachConfig = [];
if (null !== $onFulfilled) {
$eachConfig['fulfilled'] = $onFulfilled;
}
if (null !== $onRejected) {
$eachConfig['rejected'] = $onRejected;
}
if (isset($config['concurrency'])) {
$eachConfig['concurrency'] = $config['concurrency'];
}
return (new EachPromise($iterable, $eachConfig))->promise();
}
/**
@@ -40,49 +66,51 @@ final class Each
*
* $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.
* for a dynamic concurrency size.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
* @param callable $onRejected
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @return PromiseInterface
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $iterable
* @param int|(callable(int): int) $concurrency
* @param (callable(TValue, TKey, PromiseInterface<mixed, mixed>): mixed)|null $onFulfilled
* @param (callable(TReason, TKey, PromiseInterface<mixed, mixed>): mixed)|null $onRejected
*
* @return PromiseInterface<mixed, mixed>
*/
public static function ofLimit(
$iterable,
iterable $iterable,
$concurrency,
callable $onFulfilled = null,
callable $onRejected = null
) {
return (new EachPromise($iterable, [
'fulfilled' => $onFulfilled,
'rejected' => $onRejected,
'concurrency' => $concurrency
]))->promise();
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
return self::of($iterable, $onFulfilled, $onRejected, ['concurrency' => $concurrency]);
}
/**
* 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.
* Like ofLimit, but rejects the aggregate promise on the first rejection.
*
* @param mixed $iterable
* @param int|callable $concurrency
* @param callable $onFulfilled
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @return PromiseInterface
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $iterable
* @param int|(callable(int): int) $concurrency
* @param (callable(TValue, TKey, PromiseInterface<mixed, mixed>): mixed)|null $onFulfilled
*
* @return PromiseInterface<mixed, mixed>
*/
public static function ofLimitAll(
$iterable,
iterable $iterable,
$concurrency,
callable $onFulfilled = null
) {
return each_limit(
?callable $onFulfilled = null
): PromiseInterface {
return self::ofLimit(
$iterable,
$concurrency,
$onFulfilled,
function ($reason, $idx, PromiseInterface $aggregate) {
function ($reason, $idx, PromiseInterface $aggregate): void {
$aggregate->reject($reason);
}
);
@@ -1,34 +1,48 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* Represents a promise that iterates over many promises and invokes
* side-effect functions in the process.
*
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @implements PromisorInterface<mixed, mixed>
*
* @final
*/
class EachPromise implements PromisorInterface
{
private $pending = [];
use NonSerializableTrait;
private $nextPendingIndex = 0;
/** @var array<int, PromiseInterface<mixed, mixed>>|null */
private ?array $pending = [];
/** @var \Iterator|null */
private $iterable;
private int $nextPendingIndex = 0;
/** @var callable|int|null */
/** @var \Iterator<TKey, TValue|PromiseInterface<TValue, TReason>>|null */
private ?\Iterator $iterable;
/** @var (callable(int): int)|int|null */
private $concurrency;
/** @var callable|null */
/** @var (callable(TValue, TKey, PromiseInterface<mixed, mixed>): mixed)|null */
private $onFulfilled;
/** @var callable|null */
/** @var (callable(TReason, TKey, PromiseInterface<mixed, mixed>): mixed)|null */
private $onRejected;
/** @var Promise|null */
private $aggregate;
/** @var Promise<mixed, mixed>|null */
private ?Promise $aggregate = null;
/** @var bool|null */
private $mutex;
private ?bool $mutex = null;
private bool $stepWhileLocked = false;
/**
* Configuration hash can include the following key value pairs:
@@ -48,10 +62,14 @@ class EachPromise implements PromisorInterface
* 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
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $iterable Promises or values to iterate.
* @param array{
* fulfilled?: callable(TValue, TKey, PromiseInterface<mixed, mixed>): mixed,
* rejected?: callable(TReason, TKey, PromiseInterface<mixed, mixed>): mixed,
* concurrency?: int|(callable(int): int)
* } $config Configuration options
*/
public function __construct($iterable, array $config = [])
public function __construct(iterable $iterable, array $config = [])
{
$this->iterable = Create::iterFor($iterable);
@@ -68,8 +86,10 @@ class EachPromise implements PromisorInterface
}
}
/** @psalm-suppress InvalidNullableReturnType */
public function promise()
/**
* @return PromiseInterface<mixed, mixed>
*/
public function promise(): PromiseInterface
{
if ($this->aggregate) {
return $this->aggregate;
@@ -77,43 +97,56 @@ class EachPromise implements PromisorInterface
try {
$this->createPromise();
/** @psalm-assert Promise $this->aggregate */
$this->iterable->rewind();
$this->refillPending();
if (!$this->pending) {
Utils::queue()->add(function (): void {
if (!$this->aggregate || State::isSettled($this->aggregate)) {
return;
}
try {
$this->checkIfFinished();
} catch (\Throwable $e) {
$this->aggregate->reject($e);
}
});
}
} catch (\Throwable $e) {
$this->aggregate->reject($e);
} catch (\Exception $e) {
$this->aggregate->reject($e);
}
/**
* @psalm-suppress NullableReturnStatement
* @phpstan-ignore-next-line
*/
return $this->aggregate;
}
private function createPromise()
private function createPromise(): void
{
$this->mutex = false;
$this->aggregate = new Promise(function () {
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)) {
$this->aggregate = new Promise(function (): void {
while (true) {
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 (State::isSettled($this->aggregate)) {
return;
}
}
// Refill and re-sweep; give up only when nothing remains.
$this->refillPending();
if (State::isSettled($this->aggregate) || !$this->pending) {
return;
}
}
});
// Clear the references when the promise is resolved.
$clearFn = function () {
$clearFn = function (): void {
$this->iterable = $this->concurrency = $this->pending = null;
$this->onFulfilled = $this->onRejected = null;
$this->nextPendingIndex = 0;
@@ -122,18 +155,24 @@ class EachPromise implements PromisorInterface
$this->aggregate->then($clearFn, $clearFn);
}
private function refillPending()
private function refillPending(): void
{
if (!$this->concurrency) {
// Add all pending promises.
while ($this->addPending() && $this->advanceIterator());
while ($this->addPending() && $this->advanceIterator()) {
}
return;
}
// Add only up to N pending promises.
$concurrency = is_callable($this->concurrency)
? call_user_func($this->concurrency, count($this->pending))
? ($this->concurrency)(count($this->pending))
: $this->concurrency;
// The callable can settle the aggregate; admit nothing more.
if (State::isSettled($this->aggregate)) {
return;
}
$concurrency = max($concurrency - count($this->pending), 0);
// Concurrency may be set to 0 to disallow new promises.
if (!$concurrency) {
@@ -147,10 +186,11 @@ class EachPromise implements PromisorInterface
// next value to yield until promise callbacks are called.
while (--$concurrency
&& $this->advanceIterator()
&& $this->addPending());
&& $this->addPending()) {
}
}
private function addPending()
private function addPending(): bool
{
if (!$this->iterable || !$this->iterable->valid()) {
return false;
@@ -164,10 +204,9 @@ class EachPromise implements PromisorInterface
$idx = $this->nextPendingIndex++;
$this->pending[$idx] = $promise->then(
function ($value) use ($idx, $key) {
function ($value) use ($idx, $key): void {
if ($this->onFulfilled) {
call_user_func(
$this->onFulfilled,
($this->onFulfilled)(
$value,
$key,
$this->aggregate
@@ -175,10 +214,9 @@ class EachPromise implements PromisorInterface
}
$this->step($idx);
},
function ($reason) use ($idx, $key) {
function ($reason) use ($idx, $key): void {
if ($this->onRejected) {
call_user_func(
$this->onRejected,
($this->onRejected)(
$reason,
$key,
$this->aggregate
@@ -191,11 +229,13 @@ class EachPromise implements PromisorInterface
return true;
}
private function advanceIterator()
private function advanceIterator(): bool
{
// Place a lock on the iterator so that we ensure to not recurse,
// preventing fatal generator errors.
if ($this->mutex) {
$this->stepWhileLocked = true;
return false;
}
@@ -204,22 +244,28 @@ class EachPromise implements PromisorInterface
try {
$this->iterable->next();
$this->mutex = false;
return true;
} catch (\Throwable $e) {
$this->aggregate->reject($e);
$this->mutex = false;
return false;
} catch (\Exception $e) {
$this->aggregate->reject($e);
$this->mutex = false;
return false;
}
// Run the completion check that locked steps skipped.
if ($this->stepWhileLocked) {
$this->stepWhileLocked = false;
if (!State::isSettled($this->aggregate)) {
$this->checkIfFinished();
}
}
return true;
}
private function step($idx)
private function step(int $idx): void
{
// If the promise was already resolved, then ignore this step.
if (Is::settled($this->aggregate)) {
if (State::isSettled($this->aggregate)) {
return;
}
@@ -234,11 +280,13 @@ class EachPromise implements PromisorInterface
}
}
private function checkIfFinished()
/** @phpstan-impure */
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;
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -7,11 +9,22 @@ namespace GuzzleHttp\Promise;
*
* Thenning off of this promise will invoke the onFulfilled callback
* immediately and ignore other callbacks.
*
* @template TValue = mixed
* @template TReason = never
*
* @implements PromiseInterface<TValue, TReason>
*
* @final
*/
class FulfilledPromise implements PromiseInterface
{
/** @var TValue */
private $value;
/**
* @param TValue $value
*/
public function __construct($value)
{
if (is_object($value) && method_exists($value, 'then')) {
@@ -23,10 +36,21 @@ class FulfilledPromise implements PromiseInterface
$this->value = $value;
}
/**
* @template TFulfilledValue = never
* @template TFulfilledReason = never
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
* @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null $onRejected Invoked when the promise is rejected.
*
* @return ($onFulfilled is null ? self<TValue, TReason> : PromiseInterface<TFulfilledValue, TFulfilledReason|\Throwable>)
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
// Return itself if there is no onFulfilled function.
if (!$onFulfilled) {
return $this;
@@ -35,14 +59,12 @@ class FulfilledPromise implements PromiseInterface
$queue = Utils::queue();
$p = new Promise([$queue, 'run']);
$value = $this->value;
$queue->add(static function () use ($p, $value, $onFulfilled) {
if (Is::pending($p)) {
$queue->add(static function () use ($p, $value, $onFulfilled): void {
if (State::isPending($p)) {
try {
$p->resolve($onFulfilled($value));
} catch (\Throwable $e) {
$p->reject($e);
} catch (\Exception $e) {
$p->reject($e);
}
}
});
@@ -50,34 +72,39 @@ class FulfilledPromise implements PromiseInterface
return $p;
}
public function otherwise(callable $onRejected)
/**
* @param callable(TReason): mixed $onRejected Invoked when the promise is rejected.
*
* @return self<TValue, TReason>
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->then(null, $onRejected);
}
public function wait($unwrap = true, $defaultDelivery = null)
public function wait(bool $unwrap = true)
{
return $unwrap ? $this->value : null;
}
public function getState()
public function getState(): string
{
return self::FULFILLED;
}
public function resolve($value)
public function resolve($value = null): void
{
if ($value !== $this->value) {
throw new \LogicException("Cannot resolve a fulfilled promise");
throw new \LogicException('Cannot resolve a fulfilled promise');
}
}
public function reject($reason)
public function reject($reason): void
{
throw new \LogicException("Cannot reject a fulfilled promise");
throw new \LogicException('Cannot reject a fulfilled promise');
}
public function cancel()
public function cancel(): void
{
// pass
}
+57 -12
View File
@@ -1,46 +1,91 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* @deprecated Is will be removed in guzzlehttp/promises:4.0. Use State instead.
*/
final class Is
{
private function __construct()
{
}
/**
* Returns true if a promise is pending.
*
* @return bool
* @deprecated Is::pending() will be removed in guzzlehttp/promises:4.0. Use
* State::isPending() instead.
*/
public static function pending(PromiseInterface $promise)
public static function pending(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::PENDING;
\trigger_deprecation(
'guzzlehttp/promises',
'3.1',
'%s() is deprecated and will be removed in 4.0. Use %s::isPending() instead.',
__METHOD__,
State::class
);
return State::isPending($promise);
}
/**
* Returns true if a promise is fulfilled or rejected.
*
* @return bool
* @deprecated Is::settled() will be removed in guzzlehttp/promises:4.0. Use
* State::isSettled() instead.
*/
public static function settled(PromiseInterface $promise)
public static function settled(PromiseInterface $promise): bool
{
return $promise->getState() !== PromiseInterface::PENDING;
\trigger_deprecation(
'guzzlehttp/promises',
'3.1',
'%s() is deprecated and will be removed in 4.0. Use %s::isSettled() instead.',
__METHOD__,
State::class
);
return State::isSettled($promise);
}
/**
* Returns true if a promise is fulfilled.
*
* @return bool
* @deprecated Is::fulfilled() will be removed in guzzlehttp/promises:4.0.
* Use State::isFulfilled() instead.
*/
public static function fulfilled(PromiseInterface $promise)
public static function fulfilled(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::FULFILLED;
\trigger_deprecation(
'guzzlehttp/promises',
'3.1',
'%s() is deprecated and will be removed in 4.0. Use %s::isFulfilled() instead.',
__METHOD__,
State::class
);
return State::isFulfilled($promise);
}
/**
* Returns true if a promise is rejected.
*
* @return bool
* @deprecated Is::rejected() will be removed in guzzlehttp/promises:4.0.
* Use State::isRejected() instead.
*/
public static function rejected(PromiseInterface $promise)
public static function rejected(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::REJECTED;
\trigger_deprecation(
'guzzlehttp/promises',
'3.1',
'%s() is deprecated and will be removed in 4.0. Use %s::isRejected() instead.',
__METHOD__,
State::class
);
return State::isRejected($promise);
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* @internal
*/
trait NonSerializableTrait
{
public function __serialize(): array
{
throw new \LogicException(static::class.' should never be serialized');
}
public function __unserialize(array $data): void
{
throw new \LogicException(static::class.' should never be unserialized');
}
}
+83 -38
View File
@@ -1,63 +1,110 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* Promises/A+ implementation that avoids recursion when possible.
*
* @link https://promisesaplus.com/
* @template TValue = mixed
* @template TReason = mixed
*
* @implements PromiseInterface<TValue, TReason>
*
* @see https://promisesaplus.com/
*
* @final
*/
class Promise implements PromiseInterface
{
private $state = self::PENDING;
use NonSerializableTrait;
/** @var self::PENDING|self::FULFILLED|self::REJECTED */
private string $state = self::PENDING;
/** @var TValue|TReason|PromiseInterface<TValue, TReason>|null */
private $result;
/** @var (callable(): void)|null */
private $cancelFn;
/** @var (callable(bool): void)|null */
private $waitFn;
private $waitList;
private $handlers = [];
/** @var list<Promise<mixed, mixed>>|null */
private ?array $waitList = null;
/** @var list<array{0: PromiseInterface<mixed, mixed>, 1: callable|null, 2: callable|null}>|null */
private ?array $handlers = [];
/**
* @param callable $waitFn Fn that when invoked resolves the promise.
* @param callable $cancelFn Fn that when invoked cancels the promise.
* @param (callable(bool): void)|null $waitFn Fn that when invoked resolves the promise.
* @param (callable(): void)|null $cancelFn Fn that when invoked cancels the promise.
*/
public function __construct(
callable $waitFn = null,
callable $cancelFn = null
?callable $waitFn = null,
?callable $cancelFn = null
) {
$this->waitFn = $waitFn;
$this->cancelFn = $cancelFn;
}
/**
* @template TFulfilledValue = never
* @template TFulfilledReason = never
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
* @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)>
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
?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;
return $promise->then($onFulfilled, $onRejected);
}
// 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;
/** @var PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)> $promise */
$promise = $onRejected ? $rejection->then(null, $onRejected) : $rejection;
return $promise;
}
public function otherwise(callable $onRejected)
/**
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>) $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<TValue|TRejectedValue, TRejectedReason|\Throwable>
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->then(null, $onRejected);
}
public function wait($unwrap = true)
public function wait(bool $unwrap = true)
{
$this->waitIfPending();
@@ -71,14 +118,16 @@ class Promise implements PromiseInterface
// It's rejected so "unwrap" and throw an exception.
throw Create::exceptionFor($this->result);
}
return null;
}
public function getState()
public function getState(): string
{
return $this->state;
}
public function cancel()
public function cancel(): void
{
if ($this->state !== self::PENDING) {
return;
@@ -93,8 +142,6 @@ class Promise implements PromiseInterface
$fn();
} catch (\Throwable $e) {
$this->reject($e);
} catch (\Exception $e) {
$this->reject($e);
}
}
@@ -105,17 +152,17 @@ class Promise implements PromiseInterface
}
}
public function resolve($value)
public function resolve($value = null): void
{
$this->settle(self::FULFILLED, $value);
}
public function reject($reason)
public function reject($reason): void
{
$this->settle(self::REJECTED, $reason);
}
private function settle($state, $value)
private function settle(string $state, $value): void
{
if ($this->state !== self::PENDING) {
// Ignore calls with the same resolution.
@@ -148,23 +195,23 @@ class Promise implements PromiseInterface
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) {
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)) {
} elseif ($value instanceof Promise && State::isPending($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) {
static function ($value) use ($handlers): void {
foreach ($handlers as $handler) {
self::callHandler(1, $value, $handler);
}
},
static function ($reason) use ($handlers) {
static function ($reason) use ($handlers): void {
foreach ($handlers as $handler) {
self::callHandler(2, $reason, $handler);
}
@@ -180,14 +227,14 @@ class Promise implements PromiseInterface
* @param mixed $value Value to pass to the callback.
* @param array $handler Array of handler data (promise and callbacks).
*/
private static function callHandler($index, $value, array $handler)
private static function callHandler(int $index, $value, array $handler): void
{
/** @var PromiseInterface $promise */
/** @var PromiseInterface<mixed, mixed> $promise */
$promise = $handler[0];
// The promise may have been cancelled or resolved before placing
// this thunk in the queue.
if (Is::settled($promise)) {
if (State::isSettled($promise)) {
return;
}
@@ -211,12 +258,10 @@ class Promise implements PromiseInterface
}
} catch (\Throwable $reason) {
$promise->reject($reason);
} catch (\Exception $reason) {
$promise->reject($reason);
}
}
private function waitIfPending()
private function waitIfPending(): void
{
if ($this->state !== self::PENDING) {
return;
@@ -227,9 +272,9 @@ class Promise implements PromiseInterface
} 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.');
.'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();
@@ -240,13 +285,13 @@ class Promise implements PromiseInterface
}
}
private function invokeWaitFn()
private function invokeWaitFn(): void
{
try {
$wfn = $this->waitFn;
$this->waitFn = null;
$wfn(true);
} catch (\Exception $reason) {
} catch (\Throwable $reason) {
if ($this->state === self::PENDING) {
// The promise has not been resolved yet, so reject the promise
// with the exception.
@@ -259,7 +304,7 @@ class Promise implements PromiseInterface
}
}
private function invokeWaitList()
private function invokeWaitList(): void
{
$waitList = $this->waitList;
$this->waitList = null;
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -9,27 +11,35 @@ namespace GuzzleHttp\Promise;
* which registers callbacks to receive either a promise’s eventual value or
* the reason why the promise cannot be fulfilled.
*
* @link https://promisesaplus.com/
* @template TValue = mixed
* @template TReason = mixed
*
* @see https://promisesaplus.com/
*/
interface PromiseInterface
{
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
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.
* @template TFulfilledValue = never
* @template TFulfilledReason = never
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @return PromiseInterface
* @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
* @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<($onFulfilled is null ? TValue : TFulfilledValue)|($onRejected is null ? never : TRejectedValue), ($onFulfilled is null ? never : TFulfilledReason|\Throwable)|($onRejected is null ? TReason : TRejectedReason|\Throwable)>
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
);
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface;
/**
* Appends a rejection handler callback to the promise, and returns a new
@@ -37,11 +47,14 @@ interface PromiseInterface
* or to its original fulfillment value if the promise is instead
* fulfilled.
*
* @param callable $onRejected Invoked when the promise is rejected.
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @return PromiseInterface
* @param callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>) $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<TValue|TRejectedValue, TRejectedReason|\Throwable>
*/
public function otherwise(callable $onRejected);
public function otherwise(callable $onRejected): PromiseInterface;
/**
* Get the state of the promise ("pending", "rejected", or "fulfilled").
@@ -49,34 +62,45 @@ interface PromiseInterface
* The three states can be checked against the constants defined on
* PromiseInterface: PENDING, FULFILLED, and REJECTED.
*
* @return string
* The state describes how this promise was settled, not the eventual
* outcome: a promise that was resolved with another promise, directly or
* by returning one from a then() handler, reports FULFILLED while the
* inner promise may still be pending, and wait() can still throw if the
* inner promise rejects. Poll the state only on promises settled with
* plain values, or call wait() first.
*
* @return self::PENDING|self::FULFILLED|self::REJECTED
*
* @see https://github.com/guzzle/promises/issues/101
*/
public function getState();
public function getState(): string;
/**
* Resolve the promise with the given value.
* Resolve the promise with the given value, or with null if no value is given.
*
* @param mixed $value
* @param TValue|PromiseInterface<TValue, TReason>|null $value
*
* @throws \RuntimeException if the promise is already resolved.
* @throws \LogicException if the promise is already settled with a
* conflicting resolution.
*/
public function resolve($value);
public function resolve($value = null): void;
/**
* Reject the promise with the given reason.
*
* @param mixed $reason
* @param TReason $reason
*
* @throws \RuntimeException if the promise is already resolved.
* @throws \LogicException if the promise is already settled with a
* conflicting resolution.
*/
public function reject($reason);
public function reject($reason): void;
/**
* Cancels the promise if possible.
*
* @link https://github.com/promises-aplus/cancellation-spec/issues/7
* @see https://github.com/promises-aplus/cancellation-spec/issues/7
*/
public function cancel();
public function cancel(): void;
/**
* Waits until the promise completes if possible.
@@ -86,12 +110,10 @@ interface PromiseInterface
*
* If the promise cannot be waited on, then the promise will be rejected.
*
* @param bool $unwrap
*
* @return mixed
* @return ($unwrap is true ? TValue : null)
*
* @throws \LogicException if the promise has no wait function or if the
* promise does not settle after waiting.
*/
public function wait($unwrap = true);
public function wait(bool $unwrap = true);
}
@@ -1,16 +1,21 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* Interface used with classes that return a promise.
*
* @template TValue = mixed
* @template TReason = mixed
*/
interface PromisorInterface
{
/**
* Returns a promise.
*
* @return PromiseInterface
* @return PromiseInterface<TValue, TReason>
*/
public function promise();
public function promise(): PromiseInterface;
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -7,11 +9,22 @@ namespace GuzzleHttp\Promise;
*
* Thenning off of this promise will invoke the onRejected callback
* immediately and ignore other callbacks.
*
* @template TValue = never
* @template TReason = mixed
*
* @implements PromiseInterface<TValue, TReason>
*
* @final
*/
class RejectedPromise implements PromiseInterface
{
/** @var TReason */
private $reason;
/**
* @param TReason $reason
*/
public function __construct($reason)
{
if (is_object($reason) && method_exists($reason, 'then')) {
@@ -23,10 +36,21 @@ class RejectedPromise implements PromiseInterface
$this->reason = $reason;
}
/**
* @template TFulfilledValue = never
* @template TFulfilledReason = never
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param (callable(TValue): (TFulfilledValue|PromiseInterface<TFulfilledValue, TFulfilledReason>))|null $onFulfilled Invoked when the promise fulfills.
* @param (callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>))|null $onRejected Invoked when the promise is rejected.
*
* @return ($onRejected is null ? self<TValue, TReason> : PromiseInterface<TRejectedValue, TRejectedReason|\Throwable>)
*/
public function then(
callable $onFulfilled = null,
callable $onRejected = null
) {
?callable $onFulfilled = null,
?callable $onRejected = null
): PromiseInterface {
// If there's no onRejected callback then just return self.
if (!$onRejected) {
return $this;
@@ -35,17 +59,14 @@ class RejectedPromise implements PromiseInterface
$queue = Utils::queue();
$reason = $this->reason;
$p = new Promise([$queue, 'run']);
$queue->add(static function () use ($p, $reason, $onRejected) {
if (Is::pending($p)) {
$queue->add(static function () use ($p, $reason, $onRejected): void {
if (State::isPending($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);
} catch (\Exception $e) {
// onRejected threw, so return a rejected promise.
$p->reject($e);
}
}
});
@@ -53,12 +74,20 @@ class RejectedPromise implements PromiseInterface
return $p;
}
public function otherwise(callable $onRejected)
/**
* @template TRejectedValue = never
* @template TRejectedReason = never
*
* @param callable(TReason): (TRejectedValue|PromiseInterface<TRejectedValue, TRejectedReason>) $onRejected Invoked when the promise is rejected.
*
* @return PromiseInterface<TRejectedValue, TRejectedReason|\Throwable>
*/
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->then(null, $onRejected);
}
public function wait($unwrap = true, $defaultDelivery = null)
public function wait(bool $unwrap = true)
{
if ($unwrap) {
throw Create::exceptionFor($this->reason);
@@ -67,24 +96,24 @@ class RejectedPromise implements PromiseInterface
return null;
}
public function getState()
public function getState(): string
{
return self::REJECTED;
}
public function resolve($value)
public function resolve($value = null): void
{
throw new \LogicException("Cannot resolve a rejected promise");
throw new \LogicException('Cannot resolve a rejected promise');
}
public function reject($reason)
public function reject($reason): void
{
if ($reason !== $this->reason) {
throw new \LogicException("Cannot reject a rejected promise");
throw new \LogicException('Cannot reject a rejected promise');
}
}
public function cancel()
public function cancel(): void
{
// pass
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -13,24 +15,23 @@ class RejectionException extends \RuntimeException
private $reason;
/**
* @param mixed $reason Rejection reason.
* @param string $description Optional description
* @param mixed $reason Rejection reason.
* @param string|null $description Optional description.
*/
public function __construct($reason, $description = null)
public function __construct($reason, ?string $description = null)
{
$this->reason = $reason;
$message = 'The promise was rejected';
if ($description) {
$message .= ' with reason: ' . $description;
$message .= ' with reason: '.$description;
} elseif (is_string($reason)
|| (is_object($reason) && method_exists($reason, '__toString'))
) {
$message .= ' with reason: ' . $this->reason;
$message .= ' with reason: '.$this->reason;
} elseif ($reason instanceof \JsonSerializable) {
$message .= ' with reason: '
. json_encode($this->reason, JSON_PRETTY_PRINT);
$message .= ' with reason: '.json_encode($this->reason, JSON_PRETTY_PRINT);
}
parent::__construct($message);
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
final class State
{
private function __construct()
{
}
/**
* Returns true if a promise is pending.
*/
public static function isPending(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled or rejected.
*/
public static function isSettled(PromiseInterface $promise): bool
{
return $promise->getState() !== PromiseInterface::PENDING;
}
/**
* Returns true if a promise is fulfilled.
*/
public static function isFulfilled(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::FULFILLED;
}
/**
* Returns true if a promise is rejected.
*/
public static function isRejected(PromiseInterface $promise): bool
{
return $promise->getState() === PromiseInterface::REJECTED;
}
}
@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
@@ -10,16 +12,21 @@ namespace GuzzleHttp\Promise;
* 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 = [];
use NonSerializableTrait;
public function __construct($withShutdown = true)
private bool $enableShutdown = true;
/** @var list<callable(): void> */
private array $queue = [];
public function __construct(bool $withShutdown = true)
{
if ($withShutdown) {
register_shutdown_function(function () {
register_shutdown_function(function (): void {
if ($this->enableShutdown) {
// Only run the tasks if an E_ERROR didn't occur.
$err = error_get_last();
@@ -31,20 +38,23 @@ class TaskQueue implements TaskQueueInterface
}
}
public function isEmpty()
public function isEmpty(): bool
{
return !$this->queue;
}
public function add(callable $task)
/**
* @param callable(): void $task
*/
public function add(callable $task): void
{
$this->queue[] = $task;
}
public function run()
public function run(): void
{
while ($task = array_shift($this->queue)) {
/** @var callable $task */
/** @var callable(): void $task */
$task();
}
}
@@ -60,7 +70,7 @@ class TaskQueue implements TaskQueueInterface
*
* Note: This shutdown will occur before any destructors are triggered.
*/
public function disableShutdown()
public function disableShutdown(): void
{
$this->enableShutdown = false;
}
@@ -1,24 +1,26 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
interface TaskQueueInterface
{
/**
* Returns true if the queue is empty.
*
* @return bool
*/
public function isEmpty();
public function isEmpty(): bool;
/**
* Adds a task to the queue that will be executed the next time run is
* called.
*
* @param callable(): void $task
*/
public function add(callable $task);
public function add(callable $task): void;
/**
* Execute all of the pending task in the queue.
*/
public function run();
public function run(): void;
}
+186 -72
View File
@@ -1,9 +1,15 @@
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
final class Utils
{
private function __construct()
{
}
/**
* Get the global task queue used for promise resolution.
*
@@ -17,11 +23,9 @@ final class Utils
* }
* </code>
*
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
* @param TaskQueueInterface|null $assign Optionally specify a new queue instance.
*/
public static function queue(TaskQueueInterface $assign = null)
public static function queue(?TaskQueueInterface $assign = null): TaskQueueInterface
{
static $queue;
@@ -35,26 +39,26 @@ final class Utils
}
/**
* 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.
* Adds a task to the global queue and returns a promise that is fulfilled
* or rejected with the task result.
*
* @param callable $task Task function to run.
* @template TValue
*
* @return PromiseInterface
* @param callable(): TValue $task Task function to run.
*
* @return PromiseInterface<TValue, \Throwable>
*/
public static function task(callable $task)
public static function task(callable $task): PromiseInterface
{
$queue = self::queue();
$promise = new Promise([$queue, 'run']);
$queue->add(function () use ($task, $promise) {
$queue->add(function () use ($task, $promise): void {
try {
if (Is::pending($promise)) {
if (State::isPending($promise)) {
$promise->resolve($task());
}
} catch (\Throwable $e) {
$promise->reject($e);
} catch (\Exception $e) {
$promise->reject($e);
}
});
@@ -62,7 +66,7 @@ final class Utils
}
/**
* Synchronously waits on a promise to resolve and returns an inspection
* Synchronously waits on a promise to settle and returns an inspection
* state array.
*
* Returns a state associative array containing a "state" key mapping to a
@@ -71,43 +75,78 @@ final class Utils
* 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.
* @template TValue
* @template TReason
*
* @return array
* @param PromiseInterface<TValue, TReason> $promise Promise to inspect.
*
* @return array{state: PromiseInterface::FULFILLED, value: TValue}|array{state: PromiseInterface::REJECTED, reason: TReason|\Throwable}|array{state: PromiseInterface::PENDING}
*/
public static function inspect(PromiseInterface $promise)
public static function inspect(PromiseInterface $promise): array
{
$result = null;
$getResult = static function () use (&$result): ?array {
return $result;
};
$inspection = $promise->then(
static function ($value) use (&$result): void {
$result = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
static function ($reason) use (&$result): void {
$result = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
);
try {
return [
'state' => PromiseInterface::FULFILLED,
'value' => $promise->wait()
];
} catch (RejectionException $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e->getReason()];
$inspection->wait(false);
} catch (\Throwable $e) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
} catch (\Exception $e) {
$settled = $getResult();
if (null !== $settled) {
return $settled;
}
if (State::isSettled($promise)) {
try {
self::queue()->run();
} catch (\Throwable $queueError) {
return ['state' => PromiseInterface::REJECTED, 'reason' => $queueError];
}
$settled = $getResult();
if (null !== $settled) {
return $settled;
}
}
return ['state' => PromiseInterface::REJECTED, 'reason' => $e];
}
return $getResult() ?? ['state' => $promise->getState()];
}
/**
* Waits on all of the provided promises, but does not unwrap rejected
* promises as thrown exception.
* promises as a thrown exception.
*
* Returns an array of inspection state arrays.
* Returns an array of inspection state arrays keyed like the input
* iterable.
*
* @see inspect for the inspection state array format.
*
* @param PromiseInterface[] $promises Traversable of promises to wait upon.
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @return array
* @param iterable<TKey, PromiseInterface<TValue, TReason>> $promises Traversable of promises to wait upon.
*
* @return array<TKey, array{state: PromiseInterface::FULFILLED, value: TValue}|array{state: PromiseInterface::REJECTED, reason: TReason|\Throwable}|array{state: PromiseInterface::PENDING}>
*/
public static function inspectAll($promises)
public static function inspectAll(iterable $promises): array
{
$results = [];
foreach ($promises as $key => $promise) {
$results[$key] = inspect($promise);
$results[$key] = self::inspect($promise);
}
return $results;
@@ -120,14 +159,17 @@ final class Utils
* 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.
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @return array
* @param iterable<TKey, PromiseInterface<TValue, TReason>> $promises Iterable of PromiseInterface objects to wait on.
*
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
* @return array<TKey, TValue>
*
* @throws \Throwable on error
*/
public static function unwrap($promises)
public static function unwrap(iterable $promises): array
{
$results = [];
foreach ($promises as $key => $promise) {
@@ -145,34 +187,45 @@ final class Utils
* 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.
* The config array accepts a concurrency option for lazy iterables. Other
* config keys are ignored by this wrapper.
*
* @return PromiseInterface
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $promises Promises or values.
* @param bool $recursive If true, resolves newly-added entries until no unprocessed entries or pending promises remain.
* @param array{concurrency?: int|(callable(int): int)} $config Configuration options.
*
* @return PromiseInterface<array<TKey, TValue>, TReason|\Throwable>
*/
public static function all($promises, $recursive = false)
public static function all(iterable $promises, bool $recursive = false, array $config = []): PromiseInterface
{
$results = [];
$promise = Each::of(
$promises,
function ($value, $idx) use (&$results) {
function ($value, $idx) use (&$results): void {
$results[$idx] = $value;
},
function ($reason, $idx, Promise $aggregate) {
$aggregate->reject($reason);
}
function ($reason, $idx, PromiseInterface $aggregate): void {
if (State::isPending($aggregate)) {
$aggregate->reject($reason);
}
},
$config
)->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);
}
$promise = $promise->then(function ($results) use (&$promises, $config) {
if (self::shouldRecurse($promises, $results)) {
return self::all($promises, true, $config);
}
return $results;
});
}
@@ -186,25 +239,28 @@ final class Utils
*
* 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.
* winners, in the order they appear in the input.
*
* 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.
* @template TValue
* @template TReason
*
* @return PromiseInterface
* @param int $count Total number of promises.
* @param iterable<TValue|PromiseInterface<TValue, TReason>> $promises Promises or values.
*
* @return PromiseInterface<list<TValue>, \Throwable>
*/
public static function some($count, $promises)
public static function some(int $count, iterable $promises): PromiseInterface
{
$results = [];
$rejections = [];
return Each::of(
$promise = Each::of(
$promises,
function ($value, $idx, PromiseInterface $p) use (&$results, $count) {
if (Is::settled($p)) {
function ($value, $idx, PromiseInterface $p) use (&$results, $count): void {
if (State::isSettled($p)) {
return;
}
$results[$idx] = $value;
@@ -212,7 +268,7 @@ final class Utils
$p->resolve(null);
}
},
function ($reason) use (&$rejections) {
function ($reason) use (&$rejections): void {
$rejections[] = $reason;
}
)->then(
@@ -224,53 +280,111 @@ final class Utils
);
}
ksort($results);
return array_values($results);
}
);
/** @var PromiseInterface<list<TValue>, \Throwable> $promise */
return $promise;
}
/**
* 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.
* @template TValue
* @template TReason
*
* @return PromiseInterface
* @param iterable<TValue|PromiseInterface<TValue, TReason>> $promises Promises or values.
*
* @return PromiseInterface<TValue, \Throwable>
*/
public static function any($promises)
public static function any(iterable $promises): PromiseInterface
{
return self::some(1, $promises)->then(function ($values) {
return self::some(1, $promises)->then(function (array $values) {
return $values[0];
});
}
/**
* Returns a promise that is fulfilled when all of the provided promises have
* been fulfilled or rejected.
* 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.
* The returned promise is fulfilled with an array of inspection state
* arrays.
*
* The config array accepts a concurrency option for lazy iterables. Other
* config keys are ignored by this wrapper.
*
* @see inspect for the inspection state array format.
*
* @param mixed $promises Promises or values.
* @template TKey of array-key
* @template TValue
* @template TReason
*
* @return PromiseInterface
* @param iterable<TKey, TValue|PromiseInterface<TValue, TReason>> $promises Promises or values.
* @param bool $recursive If true, settles newly-added entries until no unprocessed entries or pending promises remain.
* @param array{concurrency?: int|(callable(int): int)} $config Configuration options.
*
* @return PromiseInterface<array<TKey, array{state: PromiseInterface::FULFILLED, value: TValue}|array{state: PromiseInterface::REJECTED, reason: TReason|\Throwable}>, \Throwable>
*/
public static function settle($promises)
public static function settle(iterable $promises, bool $recursive = false, array $config = []): PromiseInterface
{
$results = [];
return Each::of(
$promise = Each::of(
$promises,
function ($value, $idx) use (&$results) {
function ($value, $idx) use (&$results): void {
$results[$idx] = ['state' => PromiseInterface::FULFILLED, 'value' => $value];
},
function ($reason, $idx) use (&$results) {
function ($reason, $idx) use (&$results): void {
$results[$idx] = ['state' => PromiseInterface::REJECTED, 'reason' => $reason];
}
},
$config
)->then(function () use (&$results) {
ksort($results);
return $results;
});
if (true === $recursive) {
$promise = $promise->then(function ($results) use (&$promises, $config) {
if (self::shouldRecurse($promises, $results)) {
return self::settle($promises, true, $config);
}
return $results;
});
}
return $promise;
}
/**
* @template TKey of array-key
*
* @param iterable<TKey, mixed> $promises Promises or values.
* @param array<TKey, mixed> $results Results already collected for a pass.
*/
private static function shouldRecurse(iterable $promises, array $results): bool
{
// A consumed generator cannot be traversed again, so a recursive
// pass has nothing further to observe.
if ($promises instanceof \Generator) {
return false;
}
foreach ($promises as $key => $promise) {
if (!array_key_exists($key, $results)) {
return true;
}
if ($promise instanceof PromiseInterface && State::isPending($promise)) {
return true;
}
}
return false;
}
}
@@ -1,363 +0,0 @@
<?php
namespace GuzzleHttp\Promise;
/**
* 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\queue()->run();
* }
* </code>
*
* @param TaskQueueInterface $assign Optionally specify a new queue instance.
*
* @return TaskQueueInterface
*
* @deprecated queue will be removed in guzzlehttp/promises:2.0. Use Utils::queue instead.
*/
function queue(TaskQueueInterface $assign = null)
{
return Utils::queue($assign);
}
/**
* 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.
*
* @return PromiseInterface
*
* @deprecated task will be removed in guzzlehttp/promises:2.0. Use Utils::task instead.
*/
function task(callable $task)
{
return Utils::task($task);
}
/**
* Creates a promise for a value if the value is not a promise.
*
* @param mixed $value Promise or value.
*
* @return PromiseInterface
*
* @deprecated promise_for will be removed in guzzlehttp/promises:2.0. Use Create::promiseFor instead.
*/
function promise_for($value)
{
return Create::promiseFor($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.
*
* @return PromiseInterface
*
* @deprecated rejection_for will be removed in guzzlehttp/promises:2.0. Use Create::rejectionFor instead.
*/
function rejection_for($reason)
{
return Create::rejectionFor($reason);
}
/**
* Create an exception for a rejected promise value.
*
* @param mixed $reason
*
* @return \Exception|\Throwable
*
* @deprecated exception_for will be removed in guzzlehttp/promises:2.0. Use Create::exceptionFor instead.
*/
function exception_for($reason)
{
return Create::exceptionFor($reason);
}
/**
* Returns an iterator for the given value.
*
* @param mixed $value
*
* @return \Iterator
*
* @deprecated iter_for will be removed in guzzlehttp/promises:2.0. Use Create::iterFor instead.
*/
function iter_for($value)
{
return Create::iterFor($value);
}
/**
* 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.
*
* @return array
*
* @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspect instead.
*/
function inspect(PromiseInterface $promise)
{
return Utils::inspect($promise);
}
/**
* 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.
*
* @return array
*
* @deprecated inspect will be removed in guzzlehttp/promises:2.0. Use Utils::inspectAll instead.
*/
function inspect_all($promises)
{
return Utils::inspectAll($promises);
}
/**
* 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.
*
* @return array
*
* @throws \Exception on error
* @throws \Throwable on error in PHP >=7
*
* @deprecated unwrap will be removed in guzzlehttp/promises:2.0. Use Utils::unwrap instead.
*/
function unwrap($promises)
{
return Utils::unwrap($promises);
}
/**
* 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.
*
* @return PromiseInterface
*
* @deprecated all will be removed in guzzlehttp/promises:2.0. Use Utils::all instead.
*/
function all($promises, $recursive = false)
{
return Utils::all($promises, $recursive);
}
/**
* 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.
*
* @return PromiseInterface
*
* @deprecated some will be removed in guzzlehttp/promises:2.0. Use Utils::some instead.
*/
function some($count, $promises)
{
return Utils::some($count, $promises);
}
/**
* 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.
*
* @return PromiseInterface
*
* @deprecated any will be removed in guzzlehttp/promises:2.0. Use Utils::any instead.
*/
function any($promises)
{
return Utils::any($promises);
}
/**
* 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.
*
* @return PromiseInterface
*
* @deprecated settle will be removed in guzzlehttp/promises:2.0. Use Utils::settle instead.
*/
function settle($promises)
{
return Utils::settle($promises);
}
/**
* 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.
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*
* @deprecated each will be removed in guzzlehttp/promises:2.0. Use Each::of instead.
*/
function each(
$iterable,
callable $onFulfilled = null,
callable $onRejected = null
) {
return Each::of($iterable, $onFulfilled, $onRejected);
}
/**
* Like each, 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
* @param callable $onFulfilled
* @param callable $onRejected
*
* @return PromiseInterface
*
* @deprecated each_limit will be removed in guzzlehttp/promises:2.0. Use Each::ofLimit instead.
*/
function each_limit(
$iterable,
$concurrency,
callable $onFulfilled = null,
callable $onRejected = null
) {
return Each::ofLimit($iterable, $concurrency, $onFulfilled, $onRejected);
}
/**
* Like each_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
* @param callable $onFulfilled
*
* @return PromiseInterface
*
* @deprecated each_limit_all will be removed in guzzlehttp/promises:2.0. Use Each::ofLimitAll instead.
*/
function each_limit_all(
$iterable,
$concurrency,
callable $onFulfilled = null
) {
return Each::ofLimitAll($iterable, $concurrency, $onFulfilled);
}
/**
* Returns true if a promise is fulfilled.
*
* @return bool
*
* @deprecated is_fulfilled will be removed in guzzlehttp/promises:2.0. Use Is::fulfilled instead.
*/
function is_fulfilled(PromiseInterface $promise)
{
return Is::fulfilled($promise);
}
/**
* Returns true if a promise is rejected.
*
* @return bool
*
* @deprecated is_rejected will be removed in guzzlehttp/promises:2.0. Use Is::rejected instead.
*/
function is_rejected(PromiseInterface $promise)
{
return Is::rejected($promise);
}
/**
* Returns true if a promise is fulfilled or rejected.
*
* @return bool
*
* @deprecated is_settled will be removed in guzzlehttp/promises:2.0. Use Is::settled instead.
*/
function is_settled(PromiseInterface $promise)
{
return Is::settled($promise);
}
/**
* Create a new coroutine.
*
* @see Coroutine
*
* @return PromiseInterface
*
* @deprecated coroutine will be removed in guzzlehttp/promises:2.0. Use Coroutine::of instead.
*/
function coroutine(callable $generatorFn)
{
return Coroutine::of($generatorFn);
}
@@ -1,6 +0,0 @@
<?php
// Don't redefine the functions if included multiple times.
if (!function_exists('GuzzleHttp\Promise\promise_for')) {
require __DIR__ . '/functions.php';
}