Updated Rector to commit 706be908c5d96381785128ad94cc608814aa0f35

706be908c5 lock react/promise to avoid process leaking
This commit is contained in:
Tomas Votruba 2023-07-12 10:45:31 +00:00
parent 72beb9bc2e
commit 495c6799f3
25 changed files with 1220 additions and 980 deletions

View File

@ -19,12 +19,12 @@ final class VersionResolver
* @api
* @var string
*/
public const PACKAGE_VERSION = 'c0983f1000c1ca67b23d6da0c7f43eede90d14db';
public const PACKAGE_VERSION = '706be908c5d96381785128ad94cc608814aa0f35';
/**
* @api
* @var string
*/
public const RELEASE_DATE = '2023-07-12 10:49:32';
public const RELEASE_DATE = '2023-07-12 12:40:44';
/**
* @var int
*/

2
vendor/autoload.php vendored
View File

@ -22,4 +22,4 @@ if (PHP_VERSION_ID < 50600) {
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit1cabd29b301b8f7e32312548cb52864c::getLoader();
return ComposerAutoloaderInitde667f4000a4c705a35c8b80d1646126::getLoader();

View File

@ -722,14 +722,18 @@ return array(
'RectorPrefix202307\\React\\EventLoop\\TimerInterface' => $vendorDir . '/react/event-loop/src/TimerInterface.php',
'RectorPrefix202307\\React\\EventLoop\\Timer\\Timer' => $vendorDir . '/react/event-loop/src/Timer/Timer.php',
'RectorPrefix202307\\React\\EventLoop\\Timer\\Timers' => $vendorDir . '/react/event-loop/src/Timer/Timers.php',
'RectorPrefix202307\\React\\Promise\\CancellablePromiseInterface' => $vendorDir . '/react/promise/src/CancellablePromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\CancellationQueue' => $vendorDir . '/react/promise/src/CancellationQueue.php',
'RectorPrefix202307\\React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php',
'RectorPrefix202307\\React\\Promise\\Exception\\CompositeException' => $vendorDir . '/react/promise/src/Exception/CompositeException.php',
'RectorPrefix202307\\React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php',
'RectorPrefix202307\\React\\Promise\\Internal\\CancellationQueue' => $vendorDir . '/react/promise/src/Internal/CancellationQueue.php',
'RectorPrefix202307\\React\\Promise\\Internal\\FulfilledPromise' => $vendorDir . '/react/promise/src/Internal/FulfilledPromise.php',
'RectorPrefix202307\\React\\Promise\\Internal\\RejectedPromise' => $vendorDir . '/react/promise/src/Internal/RejectedPromise.php',
'RectorPrefix202307\\React\\Promise\\ExtendedPromiseInterface' => $vendorDir . '/react/promise/src/ExtendedPromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\FulfilledPromise' => $vendorDir . '/react/promise/src/FulfilledPromise.php',
'RectorPrefix202307\\React\\Promise\\LazyPromise' => $vendorDir . '/react/promise/src/LazyPromise.php',
'RectorPrefix202307\\React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php',
'RectorPrefix202307\\React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\PromisorInterface' => $vendorDir . '/react/promise/src/PromisorInterface.php',
'RectorPrefix202307\\React\\Promise\\RejectedPromise' => $vendorDir . '/react/promise/src/RejectedPromise.php',
'RectorPrefix202307\\React\\Promise\\UnhandledRejectionException' => $vendorDir . '/react/promise/src/UnhandledRejectionException.php',
'RectorPrefix202307\\React\\Socket\\Connection' => $vendorDir . '/react/socket/src/Connection.php',
'RectorPrefix202307\\React\\Socket\\ConnectionInterface' => $vendorDir . '/react/socket/src/ConnectionInterface.php',
'RectorPrefix202307\\React\\Socket\\Connector' => $vendorDir . '/react/socket/src/Connector.php',

View File

@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit1cabd29b301b8f7e32312548cb52864c
class ComposerAutoloaderInitde667f4000a4c705a35c8b80d1646126
{
private static $loader;
@ -22,17 +22,17 @@ class ComposerAutoloaderInit1cabd29b301b8f7e32312548cb52864c
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit1cabd29b301b8f7e32312548cb52864c', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInitde667f4000a4c705a35c8b80d1646126', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit1cabd29b301b8f7e32312548cb52864c', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInitde667f4000a4c705a35c8b80d1646126', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit1cabd29b301b8f7e32312548cb52864c::getInitializer($loader));
call_user_func(\Composer\Autoload\ComposerStaticInitde667f4000a4c705a35c8b80d1646126::getInitializer($loader));
$loader->setClassMapAuthoritative(true);
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit1cabd29b301b8f7e32312548cb52864c::$files;
$filesToLoad = \Composer\Autoload\ComposerStaticInitde667f4000a4c705a35c8b80d1646126::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

View File

@ -4,7 +4,7 @@
namespace Composer\Autoload;
class ComposerStaticInit1cabd29b301b8f7e32312548cb52864c
class ComposerStaticInitde667f4000a4c705a35c8b80d1646126
{
public static $files = array (
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
@ -974,14 +974,18 @@ class ComposerStaticInit1cabd29b301b8f7e32312548cb52864c
'RectorPrefix202307\\React\\EventLoop\\TimerInterface' => __DIR__ . '/..' . '/react/event-loop/src/TimerInterface.php',
'RectorPrefix202307\\React\\EventLoop\\Timer\\Timer' => __DIR__ . '/..' . '/react/event-loop/src/Timer/Timer.php',
'RectorPrefix202307\\React\\EventLoop\\Timer\\Timers' => __DIR__ . '/..' . '/react/event-loop/src/Timer/Timers.php',
'RectorPrefix202307\\React\\Promise\\CancellablePromiseInterface' => __DIR__ . '/..' . '/react/promise/src/CancellablePromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/CancellationQueue.php',
'RectorPrefix202307\\React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php',
'RectorPrefix202307\\React\\Promise\\Exception\\CompositeException' => __DIR__ . '/..' . '/react/promise/src/Exception/CompositeException.php',
'RectorPrefix202307\\React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php',
'RectorPrefix202307\\React\\Promise\\Internal\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/Internal/CancellationQueue.php',
'RectorPrefix202307\\React\\Promise\\Internal\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/FulfilledPromise.php',
'RectorPrefix202307\\React\\Promise\\Internal\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/RejectedPromise.php',
'RectorPrefix202307\\React\\Promise\\ExtendedPromiseInterface' => __DIR__ . '/..' . '/react/promise/src/ExtendedPromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/FulfilledPromise.php',
'RectorPrefix202307\\React\\Promise\\LazyPromise' => __DIR__ . '/..' . '/react/promise/src/LazyPromise.php',
'RectorPrefix202307\\React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php',
'RectorPrefix202307\\React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php',
'RectorPrefix202307\\React\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/react/promise/src/PromisorInterface.php',
'RectorPrefix202307\\React\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/RejectedPromise.php',
'RectorPrefix202307\\React\\Promise\\UnhandledRejectionException' => __DIR__ . '/..' . '/react/promise/src/UnhandledRejectionException.php',
'RectorPrefix202307\\React\\Socket\\Connection' => __DIR__ . '/..' . '/react/socket/src/Connection.php',
'RectorPrefix202307\\React\\Socket\\ConnectionInterface' => __DIR__ . '/..' . '/react/socket/src/ConnectionInterface.php',
'RectorPrefix202307\\React\\Socket\\Connector' => __DIR__ . '/..' . '/react/socket/src/Connector.php',
@ -3062,9 +3066,9 @@ class ComposerStaticInit1cabd29b301b8f7e32312548cb52864c
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInit1cabd29b301b8f7e32312548cb52864c::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInit1cabd29b301b8f7e32312548cb52864c::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInit1cabd29b301b8f7e32312548cb52864c::$classMap;
$loader->prefixLengthsPsr4 = ComposerStaticInitde667f4000a4c705a35c8b80d1646126::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitde667f4000a4c705a35c8b80d1646126::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitde667f4000a4c705a35c8b80d1646126::$classMap;
}, null, ClassLoader::class);
}

View File

@ -1620,27 +1620,26 @@
},
{
"name": "react\/promise",
"version": "v3.0.0",
"version_normalized": "3.0.0.0",
"version": "v2.10.0",
"version_normalized": "2.10.0.0",
"source": {
"type": "git",
"url": "https:\/\/github.com\/reactphp\/promise.git",
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4"
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38"
},
"dist": {
"type": "zip",
"url": "https:\/\/api.github.com\/repos\/reactphp\/promise\/zipball\/c86753c76fd3be465d93b308f18d189f01a22be4",
"reference": "c86753c76fd3be465d93b308f18d189f01a22be4",
"url": "https:\/\/api.github.com\/repos\/reactphp\/promise\/zipball\/f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
"reference": "f913fb8cceba1e6644b7b90c4bfb678ed8a3ef38",
"shasum": ""
},
"require": {
"php": ">=7.1.0"
"php": ">=5.4.0"
},
"require-dev": {
"phpstan\/phpstan": "1.10.20 || 1.4.10",
"phpunit\/phpunit": "^9.5 || ^7.5"
"phpunit\/phpunit": "^9.5 || ^5.7 || ^4.8.36"
},
"time": "2023-07-11T16:12:49+00:00",
"time": "2023-05-02T15:15:43+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@ -1684,7 +1683,7 @@
],
"support": {
"issues": "https:\/\/github.com\/reactphp\/promise\/issues",
"source": "https:\/\/github.com\/reactphp\/promise\/tree\/v3.0.0"
"source": "https:\/\/github.com\/reactphp\/promise\/tree\/v2.10.0"
},
"funding": [
{

File diff suppressed because one or more lines are too long

View File

@ -1,134 +1,200 @@
# Changelog
CHANGELOG for 2.x
=================
## 3.0.0 (2023-07-11)
## 2.10.0 (2023-05-02)
A major new feature release, see [**release announcement**](https://clue.engineering/2023/announcing-reactphp-promise-v3).
* Feature: Support Disjunctive Normal Form Types (DNF types) for PHP 8.2+.
(#237 by @nhedger)
* We'd like to emphasize that this component is production ready and battle-tested.
We plan to support all long-term support (LTS) releases for at least 24 months,
so you have a rock-solid foundation to build on top of.
Feature: Add full support for PHP 8.2.
(#233 by @WyriHaximus and #241 by @clue)
* The v3 release will be the way forward for this package. However, we will still
actively support v2 and v1 to provide a smooth upgrade path for those not yet
on the latest versions.
* Improve examples in documentation.
(#226 by @nhedger)
This update involves some major new features and a minor BC break over the
`v2.0.0` release. We've tried hard to avoid BC breaks where possible and
minimize impact otherwise. We expect that most consumers of this package will be
affected by BC breaks, but updating should take no longer than a few minutes.
See below for more details:
* Improve test suite and project setup and report failed assertions.
(#215 and #217 by @SimonFrings and #241 by @clue)
* BC break: PHP 8.1+ recommended, PHP 7.1+ required.
(#138 and #149 by @WyriHaximus)
* 2.9.0 (2022-02-11)
* Feature / BC break: The `PromiseInterface` now includes the functionality of the old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~.
Each promise now always includes the `then()`, `catch()`, `finally()` and `cancel()` methods.
The new `catch()` and `finally()` methods replace the deprecated ~~`otherwise()`~~ and ~~`always()`~~ methods which continue to exist for BC reasons.
The old ~~`ExtendedPromiseInterface`~~ and ~~`CancellablePromiseInterface`~~ are no longer needed and have been removed as a consequence.
(#75 by @jsor and #208 by @clue and @WyriHaximus)
* Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+).
(#198 by @cdosoftei and @SimonFrings)
```php
// old (multiple interfaces may or may not be implemented)
assert($promise instanceof PromiseInterface);
assert(method_exists($promise, 'then'));
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'otherwise')); }
if ($promise instanceof ExtendedPromiseInterface) { assert(method_exists($promise, 'always')); }
if ($promise instanceof CancellablePromiseInterface) { assert(method_exists($promise, 'cancel')); }
// new (single PromiseInterface with all methods)
assert($promise instanceof PromiseInterface);
assert(method_exists($promise, 'then'));
assert(method_exists($promise, 'catch'));
assert(method_exists($promise, 'finally'));
assert(method_exists($promise, 'cancel'));
```
```php
$promise->otherwise(function (OverflowException|UnderflowException $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
* Feature / BC break: Improve type safety of promises. Require `mixed` fulfillment value argument and `Throwable` (or `Exception`) as rejection reason.
Add PHPStan template types to ensure strict types for `resolve(T $value): PromiseInterface<T>` and `reject(Throwable $reason): PromiseInterface<never>`.
It is no longer possible to resolve a promise without a value (use `null` instead) or reject a promise without a reason (use `Throwable` instead).
(#93, #141 and #142 by @jsor, #138, #149 and #247 by @WyriHaximus and #213 and #246 by @clue)
* Feature: Support intersection types (PHP 8.1+).
(#195 by @bzikarsky)
```php
// old (arguments used to be optional)
$promise = resolve();
$promise = reject();
// new (already supported before)
$promise = resolve(null);
$promise = reject(new RuntimeException());
```
```php
$promise->otherwise(function (OverflowException&CacheException $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
* Feature / BC break: Report all unhandled rejections by default and remove ~~`done()`~~ method.
Add new `set_rejection_handler()` function to set the global rejection handler for unhandled promise rejections.
(#248, #249 and #224 by @clue)
* Improve test suite, use GitHub actions for continuous integration (CI),
update to PHPUnit 9, and add full core team to the license.
(#174, #183, #186, and #201 by @SimonFrings and #211 by @clue)
```php
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
reject(new RuntimeException('Unhandled'));
```
* 2.8.0 (2020-05-12)
* BC break: Remove all deprecated APIs and reduce API surface.
Remove ~~`some()`~~, ~~`map()`~~, ~~`reduce()`~~ functions, use `any()` and `all()` functions instead.
Remove internal ~~`FulfilledPromise`~~ and ~~`RejectedPromise`~~ classes, use `resolve()` and `reject()` functions instead.
Remove legacy promise progress API (deprecated third argument to `then()` method) and deprecated ~~`LazyPromise`~~ class.
(#32 and #98 by @jsor and #164, #219 and #220 by @clue)
* Mark `FulfilledPromise`, `RejectedPromise` and `LazyPromise` as deprecated for Promise v2 (and remove for Promise v3).
(#143 and #165 by @clue)
* BC break: Make all classes final to encourage composition over inheritance.
(#80 by @jsor)
```php
// deprecated
$fulfilled = new React\Promise\FulfilledPromise($value);
$rejected = new React\Promise\RejectedPromise($reason);
* Feature / BC break: Require `array` (or `iterable`) type for `all()` + `race()` + `any()` functions and bring in line with ES6 specification.
These functions now require a single argument with a variable number of promises or values as input.
(#225 by @clue and #35 by @jsor)
// recommended alternatives
$fulfilled = React\Promise\resolve($value);
$rejected = React\Promise\reject($reason);
```
* Fix / BC break: Fix `race()` to return a forever pending promise when called with an empty `array` (or `iterable`) and bring in line with ES6 specification.
(#83 by @jsor and #225 by @clue)
* Fix: Fix checking whether cancellable promise is an object and avoid possible warning.
(#168 by @smscr and @jsor)
* Minor performance improvements by initializing `Deferred` in the constructor and avoiding `call_user_func()` calls.
(#151 by @WyriHaximus and #171 by @Kubo2)
* Improve documentation and add docblocks to functions and interfaces.
(#135 by @CharlotteDunois)
* Minor documentation improvements.
(#110 by @seregazhuk, #132 by @CharlotteDunois, #145 by @danielecr, #178 by @WyriHaximus, #189 by @srdante, #212 by @clue, #214, #239 and #243 by @SimonFrings and #231 by @nhedger)
* Add `.gitattributes` to exclude dev files from exports.
(#154 by @reedy)
The following changes had to be ported to this release due to our branching
strategy, but also appeared in the [`2.x` branch](https://github.com/reactphp/promise/tree/2.x):
* Improve test suite, run tests on PHP 7.4 and update PHPUnit test setup.
(#163 by @clue)
* Feature: Support union types and address deprecation of `ReflectionType::getClass()` (PHP 8+).
(#197 by @cdosoftei and @SimonFrings)
* 2.7.1 (2018-01-07)
* Feature: Support intersection types (PHP 8.1+).
(#209 by @bzikarsky)
* Fix: file_exists warning when resolving with long strings.
(#130 by @sbesselsen)
* Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
(#133 by @WyriHaximus)
* Feature: Support DNS types (PHP 8.2+).
(#236 by @nhedger)
* 2.7.0 (2018-06-13)
* Feature: Port all memory improvements from `2.x` to `3.x`.
(#150 by @clue and @WyriHaximus)
* Feature: Improve memory consumption for pending promises by using static internal callbacks without binding to self.
(#124 by @clue)
* Fix: Fix checking whether cancellable promise is an object and avoid possible warning.
(#161 by @smscr)
* 2.6.0 (2018-06-11)
* Improve performance by prefixing all global functions calls with \ to skip the look up and resolve process and go straight to the global function.
(#134 by @WyriHaximus)
* Feature: Significantly improve memory consumption and performance by only passing resolver args
to resolver and canceller if callback requires them. Also use static callbacks without
binding to promise, clean up canceller function reference when they are no longer
needed and hide resolver and canceller references from call stack on PHP 7+.
(#113, #115, #116, #117, #118, #119 and #123 by @clue)
* Improve test suite, update PHPUnit and PHP versions and add `.gitattributes` to exclude dev files from exports.
(#107 by @carusogabriel, #148 and #234 by @WyriHaximus, #153 by @reedy, #162, #230 and #240 by @clue, #173, #177, #185 and #199 by @SimonFrings, #193 by @woodongwong and #210 by @bzikarsky)
These changes combined mean that rejecting promises with an `Exception` should
no longer cause any internal circular references which could cause some unexpected
memory growth in previous versions. By explicitly avoiding and explicitly
cleaning up said references, we can avoid relying on PHP's circular garbage collector
to kick in which significantly improves performance when rejecting many promises.
The following changes were originally planned for this release but later reverted
and are not part of the final release:
* Mark legacy progress support / notification API as deprecated
(#112 by @clue)
* Add iterative callback queue handler to avoid recursion (later removed to improve Fiber support).
(#28, #82 and #86 by @jsor, #158 by @WyriHaximus and #229 and #238 by @clue)
* Recommend rejecting promises by throwing an exception
(#114 by @jsor)
* Trigger an `E_USER_ERROR` instead of throwing an exception from `done()` (later removed entire `done()` method to globally report unhandled rejections).
(#97 by @jsor and #224 and #248 by @clue)
* Improve documentation to properly instantiate LazyPromise
(#121 by @holtkamp)
* Add type declarations for `some()` (later removed entire `some()` function).
(#172 by @WyriHaximus and #219 by @clue)
* Follower cancellation propagation was originally planned for this release
but has been reverted for now and is planned for a future release.
(#99 by @jsor and #122 by @clue)
## 2.0.0 (2013-12-10)
* 2.5.1 (2017-03-25)
See [`2.x` CHANGELOG](https://github.com/reactphp/promise/blob/2.x/CHANGELOG.md) for more details.
* Fix circular references when resolving with a promise which follows
itself (#94).
## 1.0.0 (2012-11-07)
* 2.5.0 (2016-12-22)
See [`1.x` CHANGELOG](https://github.com/reactphp/promise/blob/1.x/CHANGELOG.md) for more details.
* Revert automatic cancellation of pending collection promises once the
output promise resolves. This was introduced in 42d86b7 (PR #36, released
in [v2.3.0](https://github.com/reactphp/promise/releases/tag/v2.3.0)) and
was both unintended and backward incompatible.
If you need automatic cancellation, you can use something like:
```php
function allAndCancel(array $promises)
{
return \React\Promise\all($promises)
->always(function() use ($promises) {
foreach ($promises as $promise) {
if ($promise instanceof \React\Promise\CancellablePromiseInterface) {
$promise->cancel();
}
}
});
}
```
* `all()` and `map()` functions now preserve the order of the array (#77).
* Fix circular references when resolving a promise with itself (#71).
* 2.4.1 (2016-05-03)
* Fix `some()` not cancelling pending promises when too much input promises
reject (16ff799).
* 2.4.0 (2016-03-31)
* Support foreign thenables in `resolve()`.
Any object that provides a `then()` method is now assimilated to a trusted
promise that follows the state of this thenable (#52).
* Fix `some()` and `any()` for input arrays containing not enough items
(#34).
* 2.3.0 (2016-03-24)
* Allow cancellation of promises returned by functions working on promise
collections (#36).
* Handle `\Throwable` in the same way as `\Exception` (#51 by @joshdifabio).
* 2.2.2 (2016-02-26)
* Fix cancellation handlers called multiple times (#47 by @clue).
* 2.2.1 (2015-07-03)
* Fix stack error when resolving a promise in its own fulfillment or
rejection handlers.
* 2.2.0 (2014-12-30)
* Introduce new `ExtendedPromiseInterface` implemented by all promises.
* Add new `done()` method (part of the `ExtendedPromiseInterface`).
* Add new `otherwise()` method (part of the `ExtendedPromiseInterface`).
* Add new `always()` method (part of the `ExtendedPromiseInterface`).
* Add new `progress()` method (part of the `ExtendedPromiseInterface`).
* Rename `Deferred::progress` to `Deferred::notify` to avoid confusion with
`ExtendedPromiseInterface::progress` (a `Deferred::progress` alias is
still available for backward compatibility)
* `resolve()` now always returns a `ExtendedPromiseInterface`.
* 2.1.0 (2014-10-15)
* Introduce new `CancellablePromiseInterface` implemented by all promises.
* Add new `cancel()` method (part of the `CancellablePromiseInterface`).
* 2.0.0 (2013-12-10)
New major release. The goal is to streamline the API and to make it more
compliant with other promise libraries and especially with the new upcoming
[ES6 promises specification](https://github.com/domenic/promises-unwrapping/).
* Add standalone Promise class.
* Add new `race()` function.
* BC break: Bump minimum PHP version to PHP 5.4.
* BC break: Remove `ResolverInterface` and `PromiseInterface` from
`Deferred`.
* BC break: Change signature of `PromiseInterface`.
* BC break: Remove `When` and `Util` classes and move static methods to
functions.
* BC break: `FulfilledPromise` and `RejectedPromise` now throw an exception
when initialized with a promise instead of a value/reason.
* BC break: `Deferred::resolve()` and `Deferred::reject()` no longer return
a promise.

View File

@ -4,7 +4,7 @@ Promise
A lightweight implementation of
[CommonJS Promises/A](http://wiki.commonjs.org/wiki/Promises/A) for PHP.
[![CI status](https://github.com/reactphp/promise/workflows/CI/badge.svg)](https://github.com/reactphp/promise/actions)
[![CI status](https://github.com/reactphp/promise/actions/workflows/ci.yml/badge.svg?branch=2.x)](https://github.com/reactphp/promise/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/promise?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/promise)
Table of Contents
@ -19,31 +19,41 @@ Table of Contents
* [Deferred::promise()](#deferredpromise)
* [Deferred::resolve()](#deferredresolve)
* [Deferred::reject()](#deferredreject)
* [Deferred::notify()](#deferrednotify)
* [PromiseInterface](#promiseinterface)
* [PromiseInterface::then()](#promiseinterfacethen)
* [PromiseInterface::catch()](#promiseinterfacecatch)
* [PromiseInterface::finally()](#promiseinterfacefinally)
* [PromiseInterface::cancel()](#promiseinterfacecancel)
* [~~PromiseInterface::otherwise()~~](#promiseinterfaceotherwise)
* [~~PromiseInterface::always()~~](#promiseinterfacealways)
* [ExtendedPromiseInterface](#extendedpromiseinterface)
* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
* [ExtendedPromiseInterface::otherwise()](#extendedpromiseinterfaceotherwise)
* [ExtendedPromiseInterface::always()](#extendedpromiseinterfacealways)
* [ExtendedPromiseInterface::progress()](#extendedpromiseinterfaceprogress)
* [CancellablePromiseInterface](#cancellablepromiseinterface)
* [CancellablePromiseInterface::cancel()](#cancellablepromiseinterfacecancel)
* [Promise](#promise-2)
* [FulfilledPromise](#fulfilledpromise)
* [RejectedPromise](#rejectedpromise)
* [LazyPromise](#lazypromise)
* [Functions](#functions)
* [resolve()](#resolve)
* [reject()](#reject)
* [all()](#all)
* [race()](#race)
* [any()](#any)
* [set_rejection_handler()](#set_rejection_handler)
* [some()](#some)
* [map()](#map)
* [reduce()](#reduce)
* [PromisorInterface](#promisorinterface)
4. [Examples](#examples)
* [How to use Deferred](#how-to-use-deferred)
* [How promise forwarding works](#how-promise-forwarding-works)
* [Resolution forwarding](#resolution-forwarding)
* [Rejection forwarding](#rejection-forwarding)
* [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
* [Progress event forwarding](#progress-event-forwarding)
* [done() vs. then()](#done-vs-then)
5. [Install](#install)
6. [Tests](#tests)
7. [Credits](#credits)
8. [License](#license)
6. [Credits](#credits)
7. [License](#license)
Introduction
------------
@ -85,14 +95,17 @@ $deferred = new React\Promise\Deferred();
$promise = $deferred->promise();
$deferred->resolve(mixed $value);
$deferred->reject(\Throwable $reason);
$deferred->resolve(mixed $value = null);
$deferred->reject(mixed $reason = null);
$deferred->notify(mixed $update = null);
```
The `promise` method returns the promise of the deferred.
The `resolve` and `reject` methods control the state of the deferred.
The deprecated `notify` method is for progress notification.
The constructor of the `Deferred` accepts an optional `$canceller` argument.
See [Promise](#promise-2) for more information.
@ -108,7 +121,7 @@ keeping the authority to modify its state to yourself.
#### Deferred::resolve()
```php
$deferred->resolve(mixed $value);
$deferred->resolve(mixed $value = null);
```
Resolves the promise returned by `promise()`. All consumers are notified by
@ -118,12 +131,10 @@ having `$onFulfilled` (which they registered via `$promise->then()`) called with
If `$value` itself is a promise, the promise will transition to the state of
this promise once it is resolved.
See also the [`resolve()` function](#resolve).
#### Deferred::reject()
```php
$deferred->reject(\Throwable $reason);
$deferred->reject(mixed $reason = null);
```
Rejects the promise returned by `promise()`, signalling that the deferred's
@ -131,14 +142,27 @@ computation failed.
All consumers are notified by having `$onRejected` (which they registered via
`$promise->then()`) called with `$reason`.
See also the [`reject()` function](#reject).
If `$reason` itself is a promise, the promise will be rejected with the outcome
of this promise regardless whether it fulfills or rejects.
#### Deferred::notify()
> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
```php
$deferred->notify(mixed $update = null);
```
Triggers progress notifications, to indicate to consumers that the computation
is making progress toward its result.
All consumers are notified by having `$onProgress` (which they registered via
`$promise->then()`) called with `$update`.
### PromiseInterface
The promise interface provides the common interface for all promise
implementations.
See [Promise](#promise-2) for the only public implementation exposed by this
package.
A promise represents an eventual outcome, which is either fulfillment (success)
and an associated value, or rejection (failure) and an associated reason.
@ -146,22 +170,32 @@ and an associated value, or rejection (failure) and an associated reason.
Once in the fulfilled or rejected state, a promise becomes immutable.
Neither its state nor its result (or error) can be modified.
#### Implementations
* [Promise](#promise-2)
* [FulfilledPromise](#fulfilledpromise) (deprecated)
* [RejectedPromise](#rejectedpromise) (deprecated)
* [LazyPromise](#lazypromise) (deprecated)
#### PromiseInterface::then()
```php
$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null);
$transformedPromise = $promise->then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
```
Transforms a promise's value by applying a function to the promise's fulfillment
or rejection value. Returns a new promise for the transformed result.
The `then()` method registers new fulfilled and rejection handlers with a promise
(all parameters are optional):
The `then()` method registers new fulfilled, rejection and progress handlers
with a promise (all parameters are optional):
* `$onFulfilled` will be invoked once the promise is fulfilled and passed
the result as the first argument.
* `$onRejected` will be invoked once the promise is rejected and passed the
reason as the first argument.
* `$onProgress` (deprecated) will be invoked whenever the producer of the promise
triggers progress notifications and passed a single argument (whatever it
wants) to indicate progress.
It returns a new promise that will fulfill with the return value of either
`$onFulfilled` or `$onRejected`, whichever is called, or will reject with
@ -174,16 +208,51 @@ the same call to `then()`:
never both.
2. `$onFulfilled` and `$onRejected` will never be called more
than once.
3. `$onProgress` (deprecated) may be called multiple times.
#### See also
* [resolve()](#resolve) - Creating a resolved promise
* [reject()](#reject) - Creating a rejected promise
* [ExtendedPromiseInterface::done()](#extendedpromiseinterfacedone)
* [done() vs. then()](#done-vs-then)
#### PromiseInterface::catch()
### ExtendedPromiseInterface
The ExtendedPromiseInterface extends the PromiseInterface with useful shortcut
and utility methods which are not part of the Promises/A specification.
#### Implementations
* [Promise](#promise-1)
* [FulfilledPromise](#fulfilledpromise) (deprecated)
* [RejectedPromise](#rejectedpromise) (deprecated)
* [LazyPromise](#lazypromise) (deprecated)
#### ExtendedPromiseInterface::done()
```php
$promise->catch(callable $onRejected);
$promise->done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
```
Consumes the promise's ultimate value if the promise fulfills, or handles the
ultimate error.
It will cause a fatal error if either `$onFulfilled` or `$onRejected` throw or
return a rejected promise.
Since the purpose of `done()` is consumption rather than transformation,
`done()` always returns `null`.
#### See also
* [PromiseInterface::then()](#promiseinterfacethen)
* [done() vs. then()](#done-vs-then)
#### ExtendedPromiseInterface::otherwise()
```php
$promise->otherwise(callable $onRejected);
```
Registers a rejection handler for promise. It is a shortcut for:
@ -197,19 +266,19 @@ only specific errors.
```php
$promise
->catch(function (\RuntimeException $reason) {
->otherwise(function (\RuntimeException $reason) {
// Only catch \RuntimeException instances
// All other types of errors will propagate automatically
})
->catch(function (\Throwable $reason) {
->otherwise(function ($reason) {
// Catch other errors
});
)};
```
#### PromiseInterface::finally()
#### ExtendedPromiseInterface::always()
```php
$newPromise = $promise->finally(callable $onFulfilledOrRejected);
$newPromise = $promise->always(callable $onFulfilledOrRejected);
```
Allows you to execute "cleanup" type tasks in a promise chain.
@ -228,16 +297,16 @@ when the promise is either fulfilled or rejected.
rejected promise, `$newPromise` will reject with the thrown exception or
rejected promise's reason.
`finally()` behaves similarly to the synchronous finally statement. When combined
with `catch()`, `finally()` allows you to write code that is similar to the familiar
`always()` behaves similarly to the synchronous finally statement. When combined
with `otherwise()`, `always()` allows you to write code that is similar to the familiar
synchronous catch/finally pair.
Consider the following synchronous code:
```php
try {
return doSomething();
} catch (\Throwable $e) {
return doSomething();
} catch(\Exception $e) {
return handleError($e);
} finally {
cleanup();
@ -249,11 +318,31 @@ written:
```php
return doSomething()
->catch('handleError')
->finally('cleanup');
->otherwise('handleError')
->always('cleanup');
```
#### PromiseInterface::cancel()
#### ExtendedPromiseInterface::progress()
> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
```php
$promise->progress(callable $onProgress);
```
Registers a handler for progress updates from promise. It is a shortcut for:
```php
$promise->then(null, null, $onProgress);
```
### CancellablePromiseInterface
A cancellable promise provides a mechanism for consumers to notify the creator
of the promise that they are not longer interested in the result of an
operation.
#### CancellablePromiseInterface::cancel()
``` php
$promise->cancel();
@ -265,31 +354,12 @@ further interest in the results of the operation.
Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
a promise has no effect.
#### ~~PromiseInterface::otherwise()~~
#### Implementations
> Deprecated since v3.0.0, see [`catch()`](#promiseinterfacecatch) instead.
The `otherwise()` method registers a rejection handler for a promise.
This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:
```php
$promise->catch($onRejected);
```
#### ~~PromiseInterface::always()~~
> Deprecated since v3.0.0, see [`finally()`](#promiseinterfacefinally) instead.
The `always()` method allows you to execute "cleanup" type tasks in a promise chain.
This method continues to exist only for BC reasons and to ease upgrading
between versions. It is an alias for:
```php
$promise->finally($onFulfilledOrRejected);
```
* [Promise](#promise-1)
* [FulfilledPromise](#fulfilledpromise) (deprecated)
* [RejectedPromise](#rejectedpromise) (deprecated)
* [LazyPromise](#lazypromise) (deprecated)
### Promise
@ -297,14 +367,16 @@ Creates a promise whose state is controlled by the functions passed to
`$resolver`.
```php
$resolver = function (callable $resolve, callable $reject) {
$resolver = function (callable $resolve, callable $reject, callable $notify) {
// Do some work, possibly asynchronously, and then
// resolve or reject.
// resolve or reject. You can notify of progress events (deprecated)
// along the way if you want/need.
$resolve($awesomeResult);
// or throw new Exception('Promise rejected');
// or $resolve($anotherPromise);
// or $reject($nastyError);
// or $notify($progressNotification);
};
$canceller = function () {
@ -327,6 +399,7 @@ function which both will be called with 3 arguments:
fate will be equivalent to that of `$otherPromise`.
* `$reject($reason)` - Function that rejects the promise. It is recommended to
just throw an exception instead of using `$reject()`.
* `$notify($update)` - Deprecated function that issues progress events for the promise.
If the resolver or canceller throw an exception, the promise will be rejected
with that thrown exception as the rejection reason.
@ -334,13 +407,64 @@ with that thrown exception as the rejection reason.
The resolver function will be called immediately, the canceller function only
once all consumers called the `cancel()` method of the promise.
### FulfilledPromise
> Deprecated in v2.8.0: External usage of `FulfilledPromise` is deprecated, use `resolve()` instead.
Creates a already fulfilled promise.
```php
$promise = React\Promise\FulfilledPromise($value);
```
Note, that `$value` **cannot** be a promise. It's recommended to use
[resolve()](#resolve) for creating resolved promises.
### RejectedPromise
> Deprecated in v2.8.0: External usage of `RejectedPromise` is deprecated, use `reject()` instead.
Creates a already rejected promise.
```php
$promise = React\Promise\RejectedPromise($reason);
```
Note, that `$reason` **cannot** be a promise. It's recommended to use
[reject()](#reject) for creating rejected promises.
### LazyPromise
> Deprecated in v2.8.0: LazyPromise is deprecated and should not be used anymore.
Creates a promise which will be lazily initialized by `$factory` once a consumer
calls the `then()` method.
```php
$factory = function () {
$deferred = new React\Promise\Deferred();
// Do some heavy stuff here and resolve the deferred once completed
return $deferred->promise();
};
$promise = new React\Promise\LazyPromise($factory);
// $factory will only be executed once we call then()
$promise->then(function ($value) {
});
```
### Functions
Useful functions for creating and joining collections of promises.
Useful functions for creating, joining, mapping and reducing collections of
promises.
All functions working on promise collections (like `all()`, `race()`,
All functions working on promise collections (like `all()`, `race()`, `some()`
etc.) support cancellation. This means, if you call `cancel()` on the returned
promise, all promises in the collection are cancelled.
promise, all promises in the collection are cancelled. If the collection itself
is a promise which resolves to an array, this promise is also cancelled.
#### resolve()
@ -358,84 +482,33 @@ a trusted promise that follows the state of the thenable is returned.
If `$promiseOrValue` is a promise, it will be returned as is.
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
and can be consumed like any other promise:
```php
$promise = React\Promise\resolve(42);
$promise->then(function (int $result): void {
var_dump($result);
}, function (\Throwable $e): void {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
Note: The promise returned is always a promise implementing
[ExtendedPromiseInterface](#extendedpromiseinterface). If you pass in a custom
promise which only implements [PromiseInterface](#promiseinterface), this
promise will be assimilated to a extended promise following `$promiseOrValue`.
#### reject()
```php
$promise = React\Promise\reject(\Throwable $reason);
$promise = React\Promise\reject(mixed $promiseOrValue);
```
Creates a rejected promise for the supplied `$reason`.
Creates a rejected promise for the supplied `$promiseOrValue`.
Note that the [`\Throwable`](https://www.php.net/manual/en/class.throwable.php) interface introduced in PHP 7 covers
both user land [`\Exception`](https://www.php.net/manual/en/class.exception.php)'s and
[`\Error`](https://www.php.net/manual/en/class.error.php) internal PHP errors. By enforcing `\Throwable` as reason to
reject a promise, any language error or user land exception can be used to reject a promise.
If `$promiseOrValue` is a value, it will be the rejection value of the
returned promise.
The resulting `$promise` implements the [`PromiseInterface`](#promiseinterface)
and can be consumed like any other promise:
If `$promiseOrValue` is a promise, its completion value will be the rejected
value of the returned promise.
```php
$promise = React\Promise\reject(new RuntimeException('Request failed'));
$promise->then(function (int $result): void {
var_dump($result);
}, function (\Throwable $e): void {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
Note that rejected promises should always be handled similar to how any
exceptions should always be caught in a `try` + `catch` block. If you remove the
last reference to a rejected promise that has not been handled, it will
report an unhandled promise rejection:
```php
function incorrect(): int
{
$promise = React\Promise\reject(new RuntimeException('Request failed'));
// Commented out: No rejection handler registered here.
// $promise->then(null, function (\Throwable $e): void { /* ignore */ });
// Returning from a function will remove all local variable references, hence why
// this will report an unhandled promise rejection here.
return 42;
}
// Calling this function will log an error message plus its stack trace:
// Unhandled promise rejection with RuntimeException: Request failed in example.php:10
incorrect();
```
A rejected promise will be considered "handled" if you catch the rejection
reason with either the [`then()` method](#promiseinterfacethen), the
[`catch()` method](#promiseinterfacecatch), or the
[`finally()` method](#promiseinterfacefinally). Note that each of these methods
return a new promise that may again be rejected if you re-throw an exception.
A rejected promise will also be considered "handled" if you abort the operation
with the [`cancel()` method](#promiseinterfacecancel) (which in turn would
usually reject the promise if it is still pending).
See also the [`set_rejection_handler()` function](#set_rejection_handler).
This can be useful in situations where you need to reject a promise without
throwing an exception. For example, it allows you to propagate a rejection with
the value of another promise.
#### all()
```php
$promise = React\Promise\all(iterable $promisesOrValues);
$promise = React\Promise\all(array|React\Promise\PromiseInterface $promisesOrValues);
```
Returns a promise that will resolve only once all the items in
@ -446,19 +519,16 @@ will be an array containing the resolution values of each of the items in
#### race()
```php
$promise = React\Promise\race(iterable $promisesOrValues);
$promise = React\Promise\race(array|React\Promise\PromiseInterface $promisesOrValues);
```
Initiates a competitive race that allows one winner. Returns a promise which is
resolved in the same way the first settled promise resolves.
The returned promise will become **infinitely pending** if `$promisesOrValues`
contains 0 items.
#### any()
```php
$promise = React\Promise\any(iterable $promisesOrValues);
$promise = React\Promise\any(array|React\Promise\PromiseInterface $promisesOrValues);
```
Returns a promise that will resolve when any one of the items in
@ -466,52 +536,58 @@ Returns a promise that will resolve when any one of the items in
will be the resolution value of the triggering item.
The returned promise will only reject if *all* items in `$promisesOrValues` are
rejected. The rejection value will be a `React\Promise\Exception\CompositeException`
which holds all rejection reasons. The rejection reasons can be obtained with
`CompositeException::getThrowables()`.
rejected. The rejection value will be an array of all rejection reasons.
The returned promise will also reject with a `React\Promise\Exception\LengthException`
if `$promisesOrValues` contains 0 items.
#### set_rejection_handler()
#### some()
```php
React\Promise\set_rejection_handler(?callable $callback): ?callable;
$promise = React\Promise\some(array|React\Promise\PromiseInterface $promisesOrValues, integer $howMany);
```
Sets the global rejection handler for unhandled promise rejections.
Returns a promise that will resolve when `$howMany` of the supplied items in
`$promisesOrValues` resolve. The resolution value of the returned promise
will be an array of length `$howMany` containing the resolution values of the
triggering items.
Note that rejected promises should always be handled similar to how any
exceptions should always be caught in a `try` + `catch` block. If you remove
the last reference to a rejected promise that has not been handled, it will
report an unhandled promise rejection. See also the [`reject()` function](#reject)
for more details.
The returned promise will reject if it becomes impossible for `$howMany` items
to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
reject). The rejection value will be an array of
`(count($promisesOrValues) - $howMany) + 1` rejection reasons.
The `?callable $callback` argument MUST be a valid callback function that
accepts a single `Throwable` argument or a `null` value to restore the
default promise rejection handler. The return value of the callback function
will be ignored and has no effect, so you SHOULD return a `void` value. The
callback function MUST NOT throw or the program will be terminated with a
fatal error.
The returned promise will also reject with a `React\Promise\Exception\LengthException`
if `$promisesOrValues` contains less items than `$howMany`.
The function returns the previous rejection handler or `null` if using the
default promise rejection handler.
The default promise rejection handler will log an error message plus its stack
trace:
#### map()
```php
// Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
React\Promise\reject(new RuntimeException('Unhandled'));
$promise = React\Promise\map(array|React\Promise\PromiseInterface $promisesOrValues, callable $mapFunc);
```
The promise rejection handler may be used to use customize the log message or
write to custom log targets. As a rule of thumb, this function should only be
used as a last resort and promise rejections are best handled with either the
[`then()` method](#promiseinterfacethen), the
[`catch()` method](#promiseinterfacecatch), or the
[`finally()` method](#promiseinterfacefinally).
See also the [`reject()` function](#reject) for more details.
Traditional map function, similar to `array_map()`, but allows input to contain
promises and/or values, and `$mapFunc` may return either a value or a promise.
The map function receives each item as argument, where item is a fully resolved
value of a promise or value in `$promisesOrValues`.
#### reduce()
```php
$promise = React\Promise\reduce(array|React\Promise\PromiseInterface $promisesOrValues, callable $reduceFunc , $initialValue = null);
```
Traditional reduce function, similar to `array_reduce()`, but input may contain
promises and/or values, and `$reduceFunc` may return either a value or a
promise, *and* `$initialValue` may be a promise or a value for the starting
value.
### PromisorInterface
The `React\Promise\PromisorInterface` provides a common interface for objects
that provide a promise. `React\Promise\Deferred` implements it, but since it
is part of the public API anyone can implement it.
Examples
--------
@ -524,7 +600,7 @@ function getAwesomeResultPromise()
$deferred = new React\Promise\Deferred();
// Execute a Node.js-style function using the callback pattern
computeAwesomeResultAsynchronously(function (\Throwable $error, $result) use ($deferred) {
computeAwesomeResultAsynchronously(function ($error, $result) use ($deferred) {
if ($error) {
$deferred->reject($error);
} else {
@ -541,8 +617,11 @@ getAwesomeResultPromise()
function ($value) {
// Deferred resolved, do something with $value
},
function (\Throwable $reason) {
function ($reason) {
// Deferred rejected, do something with $reason
},
function ($update) {
// Progress notification triggered, do something with $update
}
);
```
@ -610,17 +689,17 @@ $deferred->promise()
->then(function ($x) {
throw new \Exception($x + 1);
})
->catch(function (\Exception $x) {
->otherwise(function (\Exception $x) {
// Propagate the rejection
throw $x;
})
->catch(function (\Exception $x) {
->otherwise(function (\Exception $x) {
// Can also propagate by returning another rejection
return React\Promise\reject(
new \Exception($x->getMessage() + 1)
);
})
->catch(function ($x) {
->otherwise(function ($x) {
echo 'Reject ' . $x->getMessage(); // 3
});
@ -642,7 +721,7 @@ $deferred->promise()
->then(function ($x) {
throw new \Exception($x + 1);
})
->catch(function (\Exception $x) {
->otherwise(function (\Exception $x) {
// Handle the rejection, and don't propagate.
// This is like catch without a rethrow
return $x->getMessage() + 1;
@ -654,58 +733,132 @@ $deferred->promise()
$deferred->resolve(1); // Prints "Mixed 4"
```
#### Progress event forwarding
> Deprecated in v2.6.0: Progress support is deprecated and should not be used anymore.
In the same way as resolution and rejection handlers, your progress handler
**MUST** return a progress event to be propagated to the next link in the chain.
If you return nothing, `null` will be propagated.
Also in the same way as resolutions and rejections, if you don't register a
progress handler, the update will be propagated through.
If your progress handler throws an exception, the exception will be propagated
to the next link in the chain. The best thing to do is to ensure your progress
handlers do not throw exceptions.
This gives you the opportunity to transform progress events at each step in the
chain so that they are meaningful to the next step. It also allows you to choose
not to transform them, and simply let them propagate untransformed, by not
registering a progress handler.
```php
$deferred = new React\Promise\Deferred();
$deferred->promise()
->progress(function ($update) {
return $update + 1;
})
->progress(function ($update) {
echo 'Progress ' . $update; // 2
});
$deferred->notify(1); // Prints "Progress 2"
```
### done() vs. then()
The golden rule is:
Either return your promise, or call done() on it.
At a first glance, `then()` and `done()` seem very similar. However, there are
important distinctions.
The intent of `then()` is to transform a promise's value and to pass or return
a new promise for the transformed value along to other parts of your code.
The intent of `done()` is to consume a promise's value, transferring
responsibility for the value to your code.
In addition to transforming a value, `then()` allows you to recover from, or
propagate intermediate errors. Any errors that are not handled will be caught
by the promise machinery and used to reject the promise returned by `then()`.
Calling `done()` transfers all responsibility for errors to your code. If an
error (either a thrown exception or returned rejection) escapes the
`$onFulfilled` or `$onRejected` callbacks you provide to done, it will be
rethrown in an uncatchable way causing a fatal error.
```php
function getJsonResult()
{
return queryApi()
->then(
// Transform API results to an object
function ($jsonResultString) {
return json_decode($jsonResultString);
},
// Transform API errors to an exception
function ($jsonErrorString) {
$object = json_decode($jsonErrorString);
throw new ApiErrorException($object->errorMessage);
}
);
}
// Here we provide no rejection handler. If the promise returned has been
// rejected, the ApiErrorException will be thrown
getJsonResult()
->done(
// Consume transformed object
function ($jsonResultObject) {
// Do something with $jsonResultObject
}
);
// Here we provide a rejection handler which will either throw while debugging
// or log the exception
getJsonResult()
->done(
function ($jsonResultObject) {
// Do something with $jsonResultObject
},
function (ApiErrorException $exception) {
if (isDebug()) {
throw $exception;
} else {
logException($exception);
}
}
);
```
Note that if a rejection value is not an instance of `\Exception`, it will be
wrapped in an exception of the type `React\Promise\UnhandledRejectionException`.
You can get the original rejection reason by calling `$exception->getReason()`.
Install
-------
The recommended way to install this library is [through Composer](https://getcomposer.org/).
The recommended way to install this library is [through Composer](https://getcomposer.org).
[New to Composer?](https://getcomposer.org/doc/00-intro.md)
This project follows [SemVer](https://semver.org/).
This will install the latest supported version from this branch:
This will install the latest supported version:
```bash
composer require react/promise:^3
composer require react/promise:^2.10
```
See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
This project aims to run on any platform and thus does not require any PHP
extensions and supports running on PHP 7.1 through current PHP 8+.
extensions and supports running on legacy PHP 5.4 through current PHP 8+ and HHVM.
It's *highly recommended to use the latest supported PHP version* for this project.
We're committed to providing long-term support (LTS) options and to provide a
smooth upgrade path. If you're using an older PHP version, you may use the
[`2.x` branch](https://github.com/reactphp/promise/tree/2.x) (PHP 5.4+) or
[`1.x` branch](https://github.com/reactphp/promise/tree/1.x) (PHP 5.3+) which both
provide a compatible API but do not take advantage of newer language features.
You may target multiple versions at the same time to support a wider range of
PHP versions like this:
```bash
composer require "react/promise:^3 || ^2 || ^1"
```
## Tests
To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):
```bash
composer install
```
To run the test suite, go to the project root and run:
```bash
vendor/bin/phpunit
```
On top of this, we use PHPStan on max level to ensure type safety across the project:
```bash
vendor/bin/phpstan
```
Credits
-------

View File

@ -25,11 +25,10 @@
}
],
"require": {
"php": ">=7.1.0"
"php": ">=5.4.0"
},
"require-dev": {
"phpstan\/phpstan": "1.10.20 || 1.4.10",
"phpunit\/phpunit": "^9.5 || ^7.5"
"phpunit\/phpunit": "^9.5 || ^5.7 || ^4.8.36"
},
"autoload": {
"psr-4": {
@ -42,13 +41,10 @@
"autoload-dev": {
"psr-4": {
"RectorPrefix202307\\React\\Promise\\": [
"tests\/fixtures\/",
"tests\/"
"tests",
"tests\/fixtures"
]
},
"files": [
"tests\/Fiber.php"
]
}
},
"keywords": [
"promise",

View File

@ -0,0 +1,17 @@
<?php
namespace RectorPrefix202307\React\Promise;
interface CancellablePromiseInterface extends PromiseInterface
{
/**
* The `cancel()` method notifies the creator of the promise that there is no
* further interest in the results of the operation.
*
* Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
* a promise has no effect.
*
* @return void
*/
public function cancel();
}

View File

@ -1,17 +1,12 @@
<?php
namespace RectorPrefix202307\React\Promise\Internal;
namespace RectorPrefix202307\React\Promise;
/**
* @internal
*/
final class CancellationQueue
class CancellationQueue
{
/** @var bool */
private $started = \false;
/** @var object[] */
private $queue = [];
public function __invoke() : void
public function __invoke()
{
if ($this->started) {
return;
@ -19,10 +14,7 @@ final class CancellationQueue
$this->started = \true;
$this->drain();
}
/**
* @param mixed $cancellable
*/
public function enqueue($cancellable) : void
public function enqueue($cancellable)
{
if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
return;
@ -32,15 +24,15 @@ final class CancellationQueue
$this->drain();
}
}
private function drain() : void
private function drain()
{
for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
$cancellable = $this->queue[$i];
\assert(\method_exists($cancellable, 'cancel'));
$exception = null;
try {
$cancellable->cancel();
} catch (\Throwable $exception) {
} catch (\Exception $exception) {
}
unset($this->queue[$i]);
if ($exception) {

View File

@ -2,42 +2,54 @@
namespace RectorPrefix202307\React\Promise;
/**
* @template T
*/
final class Deferred
class Deferred implements PromisorInterface
{
/**
* @var PromiseInterface<T>
*/
private $promise;
/** @var callable */
private $resolveCallback;
/** @var callable */
private $rejectCallback;
private $notifyCallback;
private $canceller;
public function __construct(callable $canceller = null)
{
$this->promise = new Promise(function ($resolve, $reject) : void {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
}, $canceller);
$this->canceller = $canceller;
}
/**
* @return PromiseInterface<T>
*/
public function promise() : PromiseInterface
public function promise()
{
if (null === $this->promise) {
$this->promise = new Promise(function ($resolve, $reject, $notify) {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
$this->notifyCallback = $notify;
}, $this->canceller);
$this->canceller = null;
}
return $this->promise;
}
/**
* @param T $value
*/
public function resolve($value) : void
public function resolve($value = null)
{
($this->resolveCallback)($value);
$this->promise();
\call_user_func($this->resolveCallback, $value);
}
public function reject(\Throwable $reason) : void
public function reject($reason = null)
{
($this->rejectCallback)($reason);
$this->promise();
\call_user_func($this->rejectCallback, $reason);
}
/**
* @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
* @param mixed $update
*/
public function notify($update = null)
{
$this->promise();
\call_user_func($this->notifyCallback, $update);
}
/**
* @deprecated 2.2.0
* @see Deferred::notify()
*/
public function progress($update = null)
{
$this->notify($update);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace RectorPrefix202307\React\Promise\Exception;
/**
* Represents an exception that is a composite of one or more other exceptions.
*
* This exception is useful in situations where a promise must be rejected
* with multiple exceptions. It is used for example to reject the returned
* promise from `some()` and `any()` when too many input promises reject.
*/
class CompositeException extends \Exception
{
/** @var \Throwable[] */
private $throwables;
/** @param \Throwable[] $throwables */
public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->throwables = $throwables;
}
/**
* @return \Throwable[]
*/
public function getThrowables() : array
{
return $this->throwables;
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace RectorPrefix202307\React\Promise;
interface ExtendedPromiseInterface extends PromiseInterface
{
/**
* Consumes the promise's ultimate value if the promise fulfills, or handles the
* ultimate error.
*
* It will cause a fatal error if either `$onFulfilled` or
* `$onRejected` throw or return a rejected promise.
*
* Since the purpose of `done()` is consumption rather than transformation,
* `done()` always returns `null`.
*
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @param callable|null $onProgress This argument is deprecated and should not be used anymore.
* @return void
*/
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
/**
* Registers a rejection handler for promise. It is a shortcut for:
*
* ```php
* $promise->then(null, $onRejected);
* ```
*
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
* only specific errors.
*
* @param callable $onRejected
* @return ExtendedPromiseInterface
*/
public function otherwise(callable $onRejected);
/**
* Allows you to execute "cleanup" type tasks in a promise chain.
*
* It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
* when the promise is either fulfilled or rejected.
*
* * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will fulfill with the same value as `$promise`.
* * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
* * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will reject with the same reason as `$promise`.
* * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
*
* `always()` behaves similarly to the synchronous finally statement. When combined
* with `otherwise()`, `always()` allows you to write code that is similar to the familiar
* synchronous catch/finally pair.
*
* Consider the following synchronous code:
*
* ```php
* try {
* return doSomething();
* } catch(\Exception $e) {
* return handleError($e);
* } finally {
* cleanup();
* }
* ```
*
* Similar asynchronous code (with `doSomething()` that returns a promise) can be
* written:
*
* ```php
* return doSomething()
* ->otherwise('handleError')
* ->always('cleanup');
* ```
*
* @param callable $onFulfilledOrRejected
* @return ExtendedPromiseInterface
*/
public function always(callable $onFulfilledOrRejected);
/**
* Registers a handler for progress updates from promise. It is a shortcut for:
*
* ```php
* $promise->then(null, null, $onProgress);
* ```
*
* @param callable $onProgress
* @return ExtendedPromiseInterface
* @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
*/
public function progress(callable $onProgress);
}

View File

@ -0,0 +1,60 @@
<?php
namespace RectorPrefix202307\React\Promise;
/**
* @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
*/
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $value;
public function __construct($value = null)
{
if ($value instanceof PromiseInterface) {
throw new \InvalidArgumentException('You cannot create React\\Promise\\FulfilledPromise with a promise. Use React\\Promise\\resolve($promiseOrValue) instead.');
}
$this->value = $value;
}
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onFulfilled) {
return $this;
}
try {
return resolve($onFulfilled($this->value));
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
} catch (\Exception $exception) {
return new RejectedPromise($exception);
}
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onFulfilled) {
return;
}
$result = $onFulfilled($this->value);
if ($result instanceof ExtendedPromiseInterface) {
$result->done();
}
}
public function otherwise(callable $onRejected)
{
return $this;
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(function ($value) use($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use($value) {
return $value;
});
});
}
public function progress(callable $onProgress)
{
return $this;
}
public function cancel()
{
}
}

View File

@ -1,79 +0,0 @@
<?php
namespace RectorPrefix202307\React\Promise\Internal;
use RectorPrefix202307\React\Promise\PromiseInterface;
use function RectorPrefix202307\React\Promise\resolve;
/**
* @internal
*
* @template T
* @template-implements PromiseInterface<T>
*/
final class FulfilledPromise implements PromiseInterface
{
/** @var T */
private $value;
/**
* @param T $value
* @throws \InvalidArgumentException
*/
public function __construct($value = null)
{
if ($value instanceof PromiseInterface) {
throw new \InvalidArgumentException('You cannot create React\\Promise\\FulfilledPromise with a promise. Use React\\Promise\\resolve($promiseOrValue) instead.');
}
$this->value = $value;
}
/**
* @template TFulfilled
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
*/
public function then(callable $onFulfilled = null, callable $onRejected = null) : PromiseInterface
{
if (null === $onFulfilled) {
return $this;
}
try {
/**
* @var PromiseInterface<T>|T $result
*/
$result = $onFulfilled($this->value);
return resolve($result);
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
}
}
public function catch(callable $onRejected) : PromiseInterface
{
return $this;
}
public function finally(callable $onFulfilledOrRejected) : PromiseInterface
{
return $this->then(function ($value) use($onFulfilledOrRejected) : PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use($value) {
return $value;
});
});
}
public function cancel() : void
{
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected) : PromiseInterface
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected) : PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}

View File

@ -1,111 +0,0 @@
<?php
namespace RectorPrefix202307\React\Promise\Internal;
use RectorPrefix202307\React\Promise\PromiseInterface;
use function RectorPrefix202307\React\Promise\_checkTypehint;
use function RectorPrefix202307\React\Promise\resolve;
use function RectorPrefix202307\React\Promise\set_rejection_handler;
/**
* @internal
*
* @template-implements PromiseInterface<never>
*/
final class RejectedPromise implements PromiseInterface
{
/** @var \Throwable */
private $reason;
/** @var bool */
private $handled = \false;
/**
* @param \Throwable $reason
*/
public function __construct(\Throwable $reason)
{
$this->reason = $reason;
}
/** @throws void */
public function __destruct()
{
if ($this->handled) {
return;
}
$handler = set_rejection_handler(null);
if ($handler === null) {
$message = 'Unhandled promise rejection with ' . \get_class($this->reason) . ': ' . $this->reason->getMessage() . ' in ' . $this->reason->getFile() . ':' . $this->reason->getLine() . \PHP_EOL;
$message .= 'Stack trace:' . \PHP_EOL . $this->reason->getTraceAsString();
\error_log($message);
return;
}
try {
$handler($this->reason);
} catch (\Throwable $e) {
$message = 'Fatal error: Uncaught ' . \get_class($e) . ' from unhandled promise rejection handler: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine() . \PHP_EOL;
$message .= 'Stack trace:' . \PHP_EOL . $e->getTraceAsString();
\error_log($message);
exit(255);
}
}
/**
* @template TRejected
* @param ?callable $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? never : TRejected)>
*/
public function then(callable $onFulfilled = null, callable $onRejected = null) : PromiseInterface
{
if (null === $onRejected) {
return $this;
}
$this->handled = \true;
try {
return resolve($onRejected($this->reason));
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
}
}
/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<TRejected>
*/
public function catch(callable $onRejected) : PromiseInterface
{
if (!_checkTypehint($onRejected, $this->reason)) {
return $this;
}
/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $this->then(null, $onRejected);
}
public function finally(callable $onFulfilledOrRejected) : PromiseInterface
{
return $this->then(null, function (\Throwable $reason) use($onFulfilledOrRejected) : PromiseInterface {
return resolve($onFulfilledOrRejected())->then(function () use($reason) : PromiseInterface {
return new RejectedPromise($reason);
});
});
}
public function cancel() : void
{
$this->handled = \true;
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected) : PromiseInterface
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `always()` instead
* @see self::always()
*/
public function always(callable $onFulfilledOrRejected) : PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace RectorPrefix202307\React\Promise;
/**
* @deprecated 2.8.0 LazyPromise is deprecated and should not be used anymore.
*/
class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $factory;
private $promise;
public function __construct(callable $factory)
{
$this->factory = $factory;
}
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
}
public function otherwise(callable $onRejected)
{
return $this->promise()->otherwise($onRejected);
}
public function always(callable $onFulfilledOrRejected)
{
return $this->promise()->always($onFulfilledOrRejected);
}
public function progress(callable $onProgress)
{
return $this->promise()->progress($onProgress);
}
public function cancel()
{
return $this->promise()->cancel();
}
/**
* @internal
* @see Promise::settle()
*/
public function promise()
{
if (null === $this->promise) {
try {
$this->promise = resolve(\call_user_func($this->factory));
} catch (\Throwable $exception) {
$this->promise = new RejectedPromise($exception);
} catch (\Exception $exception) {
$this->promise = new RejectedPromise($exception);
}
}
return $this->promise;
}
}

View File

@ -2,23 +2,14 @@
namespace RectorPrefix202307\React\Promise;
use RectorPrefix202307\React\Promise\Internal\RejectedPromise;
/**
* @template T
* @template-implements PromiseInterface<T>
*/
final class Promise implements PromiseInterface
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
/** @var ?callable */
private $canceller;
/** @var ?PromiseInterface<T> */
private $result;
/** @var callable[] */
private $handlers = [];
/** @var int */
private $progressHandlers = [];
private $requiredCancelRequests = 0;
/** @var bool */
private $cancelled = \false;
private $cancelRequests = 0;
public function __construct(callable $resolver, callable $canceller = null)
{
$this->canceller = $canceller;
@ -29,13 +20,13 @@ final class Promise implements PromiseInterface
$resolver = $canceller = null;
$this->call($cb);
}
public function then(callable $onFulfilled = null, callable $onRejected = null) : PromiseInterface
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->then($onFulfilled, $onRejected);
return $this->result->then($onFulfilled, $onRejected, $onProgress);
}
if (null === $this->canceller) {
return new static($this->resolver($onFulfilled, $onRejected));
return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
}
// This promise has a canceller, so we create a new child promise which
// has a canceller that invokes the parent canceller if all other
@ -44,34 +35,35 @@ final class Promise implements PromiseInterface
// keeping a cyclic reference between parent and follower.
$parent = $this;
++$parent->requiredCancelRequests;
return new static($this->resolver($onFulfilled, $onRejected), static function () use(&$parent) {
\assert($parent instanceof self);
--$parent->requiredCancelRequests;
if ($parent->requiredCancelRequests <= 0) {
return new static($this->resolver($onFulfilled, $onRejected, $onProgress), static function () use(&$parent) {
if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
$parent->cancel();
}
$parent = null;
});
}
/**
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected) : PromiseInterface
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->done($onFulfilled, $onRejected, $onProgress);
}
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use($onFulfilled, $onRejected) {
$promise->done($onFulfilled, $onRejected);
};
if ($onProgress) {
$this->progressHandlers[] = $onProgress;
}
}
public function otherwise(callable $onRejected)
{
return $this->then(null, static function ($reason) use($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new RejectedPromise($reason);
}
/**
* @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
*/
return $onRejected($reason);
});
}
public function finally(callable $onFulfilledOrRejected) : PromiseInterface
public function always(callable $onFulfilledOrRejected)
{
return $this->then(static function ($value) use($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use($value) {
@ -83,115 +75,78 @@ final class Promise implements PromiseInterface
});
});
}
public function cancel() : void
public function progress(callable $onProgress)
{
$this->cancelled = \true;
return $this->then(null, null, $onProgress);
}
public function cancel()
{
if (null === $this->canceller || null !== $this->result) {
return;
}
$canceller = $this->canceller;
$this->canceller = null;
$parentCanceller = null;
if (null !== $this->result) {
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
if ($this->result instanceof RejectedPromise) {
$this->result->cancel();
}
// Go up the promise chain and reach the top most promise which is
// itself not following another promise
$root = $this->unwrap($this->result);
// Return if the root promise is already resolved or a
// FulfilledPromise or RejectedPromise
if (!$root instanceof self || null !== $root->result) {
return;
}
$root->requiredCancelRequests--;
if ($root->requiredCancelRequests <= 0) {
$parentCanceller = [$root, 'cancel'];
}
}
if (null !== $canceller) {
$this->call($canceller);
}
// For BC, we call the parent canceller after our own canceller
if ($parentCanceller) {
$parentCanceller();
}
$this->call($canceller);
}
/**
* @deprecated 3.0.0 Use `catch()` instead
* @see self::catch()
*/
public function otherwise(callable $onRejected) : PromiseInterface
private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return $this->catch($onRejected);
}
/**
* @deprecated 3.0.0 Use `finally()` instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected) : PromiseInterface
{
return $this->finally($onFulfilledOrRejected);
}
private function resolver(callable $onFulfilled = null, callable $onRejected = null) : callable
{
return function ($resolve, $reject) use($onFulfilled, $onRejected) {
$this->handlers[] = static function (PromiseInterface $promise) use($onFulfilled, $onRejected, $resolve, $reject) {
$promise = $promise->then($onFulfilled, $onRejected);
if ($promise instanceof self && $promise->result === null) {
$promise->handlers[] = static function (PromiseInterface $promise) use($resolve, $reject) {
$promise->then($resolve, $reject);
};
} else {
$promise->then($resolve, $reject);
}
return function ($resolve, $reject, $notify) use($onFulfilled, $onRejected, $onProgress) {
if ($onProgress) {
$progressHandler = static function ($update) use($notify, $onProgress) {
try {
$notify($onProgress($update));
} catch (\Throwable $e) {
$notify($e);
} catch (\Exception $e) {
$notify($e);
}
};
} else {
$progressHandler = $notify;
}
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
$promise->then($onFulfilled, $onRejected)->done($resolve, $reject, $progressHandler);
};
$this->progressHandlers[] = $progressHandler;
};
}
private function reject(\Throwable $reason) : void
private function reject($reason = null)
{
if (null !== $this->result) {
return;
}
$this->settle(reject($reason));
}
/**
* @param PromiseInterface<T> $result
*/
private function settle(PromiseInterface $result) : void
private function settle(ExtendedPromiseInterface $promise)
{
$result = $this->unwrap($result);
if ($result === $this) {
$result = new RejectedPromise(new \LogicException('Cannot resolve a promise with itself.'));
}
if ($result instanceof self) {
$result->requiredCancelRequests++;
} else {
// Unset canceller only when not following a pending promise
$this->canceller = null;
$promise = $this->unwrap($promise);
if ($promise === $this) {
$promise = new RejectedPromise(new \LogicException('Cannot resolve a promise with itself.'));
}
$handlers = $this->handlers;
$this->handlers = [];
$this->result = $result;
$this->progressHandlers = $this->handlers = [];
$this->result = $promise;
$this->canceller = null;
foreach ($handlers as $handler) {
$handler($result);
}
// Forward cancellation to rejected promise to avoid reporting unhandled rejection
if ($this->cancelled && $result instanceof RejectedPromise) {
$result->cancel();
$handler($promise);
}
}
/**
* @param PromiseInterface<T> $promise
* @return PromiseInterface<T>
*/
private function unwrap(PromiseInterface $promise) : PromiseInterface
private function unwrap($promise)
{
$promise = $this->extract($promise);
while ($promise instanceof self && null !== $promise->result) {
/** @var PromiseInterface<T> $promise */
$promise = $promise->result;
$promise = $this->extract($promise->result);
}
return $promise;
}
private function call(callable $cb) : void
private function extract($promise)
{
if ($promise instanceof LazyPromise) {
$promise = $promise->promise();
}
return $promise;
}
private function call(callable $cb)
{
// Explicitly overwrite argument with null value. This ensure that this
// argument does not show up in the stack trace in PHP 7+ only.
@ -207,7 +162,6 @@ final class Promise implements PromiseInterface
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$ref = new \ReflectionMethod($callback, '__invoke');
} else {
\assert($callback instanceof \Closure || \is_string($callback));
$ref = new \ReflectionFunction($callback);
}
$args = $ref->getNumberOfParameters();
@ -224,21 +178,29 @@ final class Promise implements PromiseInterface
// These assumptions are covered by the test suite, so if you ever feel like
// refactoring this, go ahead, any alternative suggestions are welcome!
$target =& $this;
$callback(static function ($value) use(&$target) {
$progressHandlers =& $this->progressHandlers;
$callback(static function ($value = null) use(&$target) {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
}, static function (\Throwable $reason) use(&$target) {
}, static function ($reason = null) use(&$target) {
if ($target !== null) {
$target->reject($reason);
$target = null;
}
}, static function ($update = null) use(&$progressHandlers) {
foreach ($progressHandlers as $handler) {
$handler($update);
}
});
}
} catch (\Throwable $e) {
$target = null;
$this->reject($e);
} catch (\Exception $e) {
$target = null;
$this->reject($e);
}
}
}

View File

@ -2,9 +2,6 @@
namespace RectorPrefix202307\React\Promise;
/**
* @template-covariant T
*/
interface PromiseInterface
{
/**
@ -18,6 +15,9 @@ interface PromiseInterface
* the result as the first argument.
* * `$onRejected` will be invoked once the promise is rejected and passed the
* reason as the first argument.
* * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
* triggers progress notifications and passed a single argument (whatever it
* wants) to indicate progress.
*
* It returns a new promise that will fulfill with the return value of either
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
@ -30,118 +30,12 @@ interface PromiseInterface
* never both.
* 2. `$onFulfilled` and `$onRejected` will never be called more
* than once.
* 3. `$onProgress` (deprecated) may be called multiple times.
*
* @template TFulfilled
* @template TRejected
* @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
* @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
* @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @param callable|null $onProgress This argument is deprecated and should not be used anymore.
* @return PromiseInterface
*/
public function then(?callable $onFulfilled = null, ?callable $onRejected = null) : PromiseInterface;
/**
* Registers a rejection handler for promise. It is a shortcut for:
*
* ```php
* $promise->then(null, $onRejected);
* ```
*
* Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
* only specific errors.
*
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
*/
public function catch(callable $onRejected) : PromiseInterface;
/**
* Allows you to execute "cleanup" type tasks in a promise chain.
*
* It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
* when the promise is either fulfilled or rejected.
*
* * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will fulfill with the same value as `$promise`.
* * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
* * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
* `$newPromise` will reject with the same reason as `$promise`.
* * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
* rejected promise, `$newPromise` will reject with the thrown exception or
* rejected promise's reason.
*
* `finally()` behaves similarly to the synchronous finally statement. When combined
* with `catch()`, `finally()` allows you to write code that is similar to the familiar
* synchronous catch/finally pair.
*
* Consider the following synchronous code:
*
* ```php
* try {
* return doSomething();
* } catch(\Exception $e) {
* return handleError($e);
* } finally {
* cleanup();
* }
* ```
*
* Similar asynchronous code (with `doSomething()` that returns a promise) can be
* written:
*
* ```php
* return doSomething()
* ->catch('handleError')
* ->finally('cleanup');
* ```
*
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
*/
public function finally(callable $onFulfilledOrRejected) : PromiseInterface;
/**
* The `cancel()` method notifies the creator of the promise that there is no
* further interest in the results of the operation.
*
* Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
* a promise has no effect.
*
* @return void
*/
public function cancel() : void;
/**
* [Deprecated] Registers a rejection handler for a promise.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->catch($onRejected);
* ```
*
* @template TThrowable of \Throwable
* @template TRejected
* @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
* @return PromiseInterface<T|TRejected>
* @deprecated 3.0.0 Use catch() instead
* @see self::catch()
*/
public function otherwise(callable $onRejected) : PromiseInterface;
/**
* [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
*
* This method continues to exist only for BC reasons and to ease upgrading
* between versions. It is an alias for:
*
* ```php
* $promise->finally($onFulfilledOrRejected);
* ```
*
* @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
* @return PromiseInterface<T>
* @deprecated 3.0.0 Use finally() instead
* @see self::finally()
*/
public function always(callable $onFulfilledOrRejected) : PromiseInterface;
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
}

View File

@ -0,0 +1,13 @@
<?php
namespace RectorPrefix202307\React\Promise;
interface PromisorInterface
{
/**
* Returns the promise of the deferred.
*
* @return PromiseInterface
*/
public function promise();
}

View File

@ -0,0 +1,66 @@
<?php
namespace RectorPrefix202307\React\Promise;
/**
* @deprecated 2.8.0 External usage of RejectedPromise is deprecated, use `reject()` instead.
*/
class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $reason;
public function __construct($reason = null)
{
if ($reason instanceof PromiseInterface) {
throw new \InvalidArgumentException('You cannot create React\\Promise\\RejectedPromise with a promise. Use React\\Promise\\reject($promiseOrValue) instead.');
}
$this->reason = $reason;
}
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onRejected) {
return $this;
}
try {
return resolve($onRejected($this->reason));
} catch (\Throwable $exception) {
return new RejectedPromise($exception);
} catch (\Exception $exception) {
return new RejectedPromise($exception);
}
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null === $onRejected) {
throw UnhandledRejectionException::resolve($this->reason);
}
$result = $onRejected($this->reason);
if ($result instanceof self) {
throw UnhandledRejectionException::resolve($result->reason);
}
if ($result instanceof ExtendedPromiseInterface) {
$result->done();
}
}
public function otherwise(callable $onRejected)
{
if (!_checkTypehint($onRejected, $this->reason)) {
return $this;
}
return $this->then(null, $onRejected);
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(null, function ($reason) use($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use($reason) {
return new RejectedPromise($reason);
});
});
}
public function progress(callable $onProgress)
{
return $this;
}
public function cancel()
{
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace RectorPrefix202307\React\Promise;
class UnhandledRejectionException extends \RuntimeException
{
private $reason;
public static function resolve($reason)
{
if ($reason instanceof \Exception || $reason instanceof \Throwable) {
return $reason;
}
return new static($reason);
}
public function __construct($reason)
{
$this->reason = $reason;
$message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));
parent::__construct($message, 0);
}
public function getReason()
{
return $this->reason;
}
}

View File

@ -2,9 +2,6 @@
namespace RectorPrefix202307\React\Promise;
use RectorPrefix202307\React\Promise\Exception\CompositeException;
use RectorPrefix202307\React\Promise\Internal\FulfilledPromise;
use RectorPrefix202307\React\Promise\Internal\RejectedPromise;
/**
* Creates a promise for the supplied `$promiseOrValue`.
*
@ -16,45 +13,51 @@ use RectorPrefix202307\React\Promise\Internal\RejectedPromise;
*
* If `$promiseOrValue` is a promise, it will be returned as is.
*
* @template T
* @param PromiseInterface<T>|T $promiseOrValue
* @return PromiseInterface<T>
* @param mixed $promiseOrValue
* @return PromiseInterface
*/
function resolve($promiseOrValue) : PromiseInterface
function resolve($promiseOrValue = null)
{
if ($promiseOrValue instanceof PromiseInterface) {
if ($promiseOrValue instanceof ExtendedPromiseInterface) {
return $promiseOrValue;
}
// Check is_object() first to avoid method_exists() triggering
// class autoloaders if $promiseOrValue is a string.
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
$canceller = null;
if (\method_exists($promiseOrValue, 'cancel')) {
$canceller = [$promiseOrValue, 'cancel'];
\assert(\is_callable($canceller));
}
return new Promise(function ($resolve, $reject) use($promiseOrValue) : void {
$promiseOrValue->then($resolve, $reject);
return new Promise(function ($resolve, $reject, $notify) use($promiseOrValue) {
$promiseOrValue->then($resolve, $reject, $notify);
}, $canceller);
}
return new FulfilledPromise($promiseOrValue);
}
/**
* Creates a rejected promise for the supplied `$reason`.
* Creates a rejected promise for the supplied `$promiseOrValue`.
*
* If `$reason` is a value, it will be the rejection value of the
* If `$promiseOrValue` is a value, it will be the rejection value of the
* returned promise.
*
* If `$reason` is a promise, its completion value will be the rejected
* If `$promiseOrValue` is a promise, its completion value will be the rejected
* value of the returned promise.
*
* This can be useful in situations where you need to reject a promise without
* throwing an exception. For example, it allows you to propagate a rejection with
* the value of another promise.
*
* @return PromiseInterface<never>
* @param mixed $promiseOrValue
* @return PromiseInterface
*/
function reject(\Throwable $reason) : PromiseInterface
function reject($promiseOrValue = null)
{
return new RejectedPromise($reason);
if ($promiseOrValue instanceof PromiseInterface) {
return resolve($promiseOrValue)->then(function ($value) {
return new RejectedPromise($value);
});
}
return new RejectedPromise($promiseOrValue);
}
/**
* Returns a promise that will resolve only once all the items in
@ -62,40 +65,14 @@ function reject(\Throwable $reason) : PromiseInterface
* will be an array containing the resolution values of each of the items in
* `$promisesOrValues`.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<array<T>>
* @param array $promisesOrValues
* @return PromiseInterface
*/
function all(iterable $promisesOrValues) : PromiseInterface
function all($promisesOrValues)
{
$cancellationQueue = new Internal\CancellationQueue();
return new Promise(function ($resolve, $reject) use($promisesOrValues, $cancellationQueue) : void {
$toResolve = 0;
/** @var bool */
$continue = \true;
$values = [];
foreach ($promisesOrValues as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
$values[$i] = null;
++$toResolve;
resolve($promiseOrValue)->then(function ($value) use($i, &$values, &$toResolve, &$continue, $resolve) : void {
$values[$i] = $value;
if (0 === --$toResolve && !$continue) {
$resolve($values);
}
}, function (\Throwable $reason) use(&$continue, $reject) : void {
$continue = \false;
$reject($reason);
});
if (!$continue && !\is_array($promisesOrValues)) {
break;
}
}
$continue = \false;
if ($toResolve === 0) {
$resolve($values);
}
}, $cancellationQueue);
return map($promisesOrValues, function ($val) {
return $val;
});
}
/**
* Initiates a competitive race that allows one winner. Returns a promise which is
@ -104,24 +81,24 @@ function all(iterable $promisesOrValues) : PromiseInterface
* The returned promise will become **infinitely pending** if `$promisesOrValues`
* contains 0 items.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
* @param array $promisesOrValues
* @return PromiseInterface
*/
function race(iterable $promisesOrValues) : PromiseInterface
function race($promisesOrValues)
{
$cancellationQueue = new Internal\CancellationQueue();
return new Promise(function (callable $resolve, callable $reject) use($promisesOrValues, $cancellationQueue) : void {
$continue = \true;
foreach ($promisesOrValues as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use(&$continue) : void {
$continue = \false;
});
if (!$continue && !\is_array($promisesOrValues)) {
break;
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use($promisesOrValues, $cancellationQueue) {
resolve($promisesOrValues)->done(function ($array) use($cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array) || !$array) {
$resolve();
return;
}
}
foreach ($array as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)->done($resolve, $reject, $notify);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
@ -135,97 +112,162 @@ function race(iterable $promisesOrValues) : PromiseInterface
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
* if `$promisesOrValues` contains 0 items.
*
* @template T
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
* @return PromiseInterface<T>
* @param array $promisesOrValues
* @return PromiseInterface
*/
function any(iterable $promisesOrValues) : PromiseInterface
function any($promisesOrValues)
{
$cancellationQueue = new Internal\CancellationQueue();
return new Promise(function ($resolve, $reject) use($promisesOrValues, $cancellationQueue) : void {
$toReject = 0;
$continue = \true;
$reasons = [];
foreach ($promisesOrValues as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
++$toReject;
resolve($promiseOrValue)->then(function ($value) use($resolve, &$continue) : void {
$continue = \false;
$resolve($value);
}, function (\Throwable $reason) use($i, &$reasons, &$toReject, $reject, &$continue) : void {
$reasons[$i] = $reason;
if (0 === --$toReject && !$continue) {
$reject(new CompositeException($reasons, 'All promises rejected.'));
}
});
if (!$continue && !\is_array($promisesOrValues)) {
break;
return some($promisesOrValues, 1)->then(function ($val) {
return \array_shift($val);
});
}
/**
* Returns a promise that will resolve when `$howMany` of the supplied items in
* `$promisesOrValues` resolve. The resolution value of the returned promise
* will be an array of length `$howMany` containing the resolution values of the
* triggering items.
*
* The returned promise will reject if it becomes impossible for `$howMany` items
* to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
* reject). The rejection value will be an array of
* `(count($promisesOrValues) - $howMany) + 1` rejection reasons.
*
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
* if `$promisesOrValues` contains less items than `$howMany`.
*
* @param array $promisesOrValues
* @param int $howMany
* @return PromiseInterface
*/
function some($promisesOrValues, $howMany)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use($promisesOrValues, $howMany, $cancellationQueue) {
resolve($promisesOrValues)->done(function ($array) use($howMany, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array) || $howMany < 1) {
$resolve([]);
return;
}
}
$continue = \false;
if ($toReject === 0 && !$reasons) {
$reject(new Exception\LengthException('Must contain at least 1 item but contains only 0 items.'));
} elseif ($toReject === 0) {
$reject(new CompositeException($reasons, 'All promises rejected.'));
}
$len = \count($array);
if ($len < $howMany) {
throw new Exception\LengthException(\sprintf('Input array must contain at least %d item%s but contains only %s item%s.', $howMany, 1 === $howMany ? '' : 's', $len, 1 === $len ? '' : 's'));
}
$toResolve = $howMany;
$toReject = $len - $toResolve + 1;
$values = [];
$reasons = [];
foreach ($array as $i => $promiseOrValue) {
$fulfiller = function ($val) use($i, &$values, &$toResolve, $toReject, $resolve) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
$values[$i] = $val;
if (0 === --$toResolve) {
$resolve($values);
}
};
$rejecter = function ($reason) use($i, &$reasons, &$toReject, $toResolve, $reject) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
$reasons[$i] = $reason;
if (0 === --$toReject) {
$reject($reasons);
}
};
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)->done($fulfiller, $rejecter, $notify);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* Sets the global rejection handler for unhandled promise rejections.
* Traditional map function, similar to `array_map()`, but allows input to contain
* promises and/or values, and `$mapFunc` may return either a value or a promise.
*
* Note that rejected promises should always be handled similar to how any
* exceptions should always be caught in a `try` + `catch` block. If you remove
* the last reference to a rejected promise that has not been handled, it will
* report an unhandled promise rejection. See also the [`reject()` function](#reject)
* for more details.
* The map function receives each item as argument, where item is a fully resolved
* value of a promise or value in `$promisesOrValues`.
*
* The `?callable $callback` argument MUST be a valid callback function that
* accepts a single `Throwable` argument or a `null` value to restore the
* default promise rejection handler. The return value of the callback function
* will be ignored and has no effect, so you SHOULD return a `void` value. The
* callback function MUST NOT throw or the program will be terminated with a
* fatal error.
*
* The function returns the previous rejection handler or `null` if using the
* default promise rejection handler.
*
* The default promise rejection handler will log an error message plus its
* stack trace:
*
* ```php
* // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
* React\Promise\reject(new RuntimeException('Unhandled'));
* ```
*
* The promise rejection handler may be used to use customize the log message or
* write to custom log targets. As a rule of thumb, this function should only be
* used as a last resort and promise rejections are best handled with either the
* [`then()` method](#promiseinterfacethen), the
* [`catch()` method](#promiseinterfacecatch), or the
* [`finally()` method](#promiseinterfacefinally).
* See also the [`reject()` function](#reject) for more details.
*
* @param callable(\Throwable):void|null $callback
* @return callable(\Throwable):void|null
* @param array $promisesOrValues
* @param callable $mapFunc
* @return PromiseInterface
*/
function set_rejection_handler(?callable $callback) : ?callable
function map($promisesOrValues, callable $mapFunc)
{
static $current = null;
$previous = $current;
$current = $callback;
return $previous;
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use($promisesOrValues, $mapFunc, $cancellationQueue) {
resolve($promisesOrValues)->done(function ($array) use($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array) || !$array) {
$resolve([]);
return;
}
$toResolve = \count($array);
$values = [];
foreach ($array as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
$values[$i] = null;
resolve($promiseOrValue)->then($mapFunc)->done(function ($mapped) use($i, &$values, &$toResolve, $resolve) {
$values[$i] = $mapped;
if (0 === --$toResolve) {
$resolve($values);
}
}, $reject, $notify);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* Traditional reduce function, similar to `array_reduce()`, but input may contain
* promises and/or values, and `$reduceFunc` may return either a value or a
* promise, *and* `$initialValue` may be a promise or a value for the starting
* value.
*
* @param array $promisesOrValues
* @param callable $reduceFunc
* @param mixed $initialValue
* @return PromiseInterface
*/
function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
resolve($promisesOrValues)->done(function ($array) use($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array)) {
$array = [];
}
$total = \count($array);
$i = 0;
// Wrap the supplied $reduceFunc with one that handles promises and then
// delegates to the supplied.
$wrappedReduceFunc = function ($current, $val) use($reduceFunc, $cancellationQueue, $total, &$i) {
$cancellationQueue->enqueue($val);
return $current->then(function ($c) use($reduceFunc, $total, &$i, $val) {
return resolve($val)->then(function ($value) use($reduceFunc, $total, &$i, $c) {
return $reduceFunc($c, $value, $i++, $total);
});
});
};
$cancellationQueue->enqueue($initialValue);
\array_reduce($array, $wrappedReduceFunc, resolve($initialValue))->done($resolve, $reject, $notify);
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* @internal
*/
function _checkTypehint(callable $callback, \Throwable $reason) : bool
function _checkTypehint(callable $callback, $object)
{
if (!\is_object($object)) {
return \true;
}
if (\is_array($callback)) {
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
} else {
\assert($callback instanceof \Closure || \is_string($callback));
$callbackReflection = new \ReflectionFunction($callback);
}
$parameters = $callbackReflection->getParameters();
@ -233,6 +275,13 @@ function _checkTypehint(callable $callback, \Throwable $reason) : bool
return \true;
}
$expectedException = $parameters[0];
// PHP before v8 used an easy API:
if (\PHP_VERSION_ID < 70100 || \defined('RectorPrefix202307\\HHVM_VERSION')) {
if (!$expectedException->getClass()) {
return \true;
}
return $expectedException->getClass()->isInstance($object);
}
// Extract the type of the argument and handle different possibilities
$type = $expectedException->getType();
$isTypeUnion = \true;
@ -258,17 +307,12 @@ function _checkTypehint(callable $callback, \Throwable $reason) : bool
foreach ($types as $type) {
if ($type instanceof \ReflectionIntersectionType) {
foreach ($type->getTypes() as $typeToMatch) {
\assert($typeToMatch instanceof \ReflectionNamedType);
$name = $typeToMatch->getName();
if (!($matches = !$typeToMatch->isBuiltin() && $reason instanceof $name)) {
if (!($matches = $typeToMatch->isBuiltin() && \gettype($object) === $typeToMatch->getName() || (new \ReflectionClass($typeToMatch->getName()))->isInstance($object))) {
break;
}
}
\assert(isset($matches));
} else {
\assert($type instanceof \ReflectionNamedType);
$name = $type->getName();
$matches = !$type->isBuiltin() && $reason instanceof $name;
$matches = $type->isBuiltin() && \gettype($object) === $type->getName() || (new \ReflectionClass($type->getName()))->isInstance($object);
}
// If we look for a single match (union), we can return early on match
// If we look for a full match (intersection), we can return early on mismatch