This commit is contained in:
2024-11-27 21:34:07 +02:00
parent 638bcba894
commit b6d1215999
190 changed files with 31518 additions and 0 deletions

112
vendor/react/async/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,112 @@
# Changelog
## 4.3.0 (2024-06-04)
* Feature: Improve performance by avoiding unneeded references in `FiberMap`.
(#88 by @clue)
* Feature: Improve PHP 8.4+ support by avoiding implicitly nullable type declarations.
(#87 by @clue)
* Improve type safety for test environment.
(#86 by @SimonFrings)
## 4.2.0 (2023-11-22)
* Feature: Add Promise v3 template types for all public functions.
(#40 by @WyriHaximus and @clue)
All our public APIs now use Promise v3 template types to guide IDEs and static
analysis tools (like PHPStan), helping with proper type usage and improving
code quality:
```php
assertType('bool', await(resolve(true)));
assertType('PromiseInterface<bool>', async(fn(): bool => true)());
assertType('PromiseInterface<bool>', coroutine(fn(): bool => true));
```
* Feature: Full PHP 8.3 compatibility.
(#81 by @clue)
* Update test suite to avoid unhandled promise rejections.
(#79 by @clue)
## 4.1.0 (2023-06-22)
* Feature: Add new `delay()` function to delay program execution.
(#69 and #78 by @clue)
```php
echo 'a';
Loop::addTimer(1.0, function () {
echo 'b';
});
React\Async\delay(3.0);
echo 'c';
// prints "a" at t=0.0s
// prints "b" at t=1.0s
// prints "c" at t=3.0s
```
* Update test suite, add PHPStan with `max` level and report failed assertions.
(#66 and #76 by @clue and #61 and #73 by @WyriHaximus)
## 4.0.0 (2022-07-11)
A major new feature release, see [**release announcement**](https://clue.engineering/2022/announcing-reactphp-async).
* 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.
* The v4 release will be the way forward for this package. However, we will still
actively support v3 and v2 to provide a smooth upgrade path for those not yet
on PHP 8.1+. If you're using an older PHP version, you may use either version
which all provide a compatible API but may not take advantage of newer language
features. You may target multiple versions at the same time to support a wider range of
PHP versions:
* [`4.x` branch](https://github.com/reactphp/async/tree/4.x) (PHP 8.1+)
* [`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+)
* [`2.x` branch](https://github.com/reactphp/async/tree/2.x) (PHP 5.3+)
This update involves some major new features and a minor BC break over the
`v3.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:
* Feature / BC break: Require PHP 8.1+ and add `mixed` type declarations.
(#14 by @clue)
* Feature: Add Fiber-based `async()` and `await()` functions.
(#15, #18, #19 and #20 by @WyriHaximus and #26, #28, #30, #32, #34, #55 and #57 by @clue)
* Project maintenance, rename `main` branch to `4.x` and update installation instructions.
(#29 by @clue)
The following changes had to be ported to this release due to our branching
strategy, but also appeared in the `v3.0.0` release:
* Feature: Support iterable type for `parallel()` + `series()` + `waterfall()`.
(#49 by @clue)
* Feature: Forward compatibility with upcoming Promise v3.
(#48 by @clue)
* Minor documentation improvements.
(#36 by @SimonFrings and #51 by @nhedger)
## 3.0.0 (2022-07-11)
See [`3.x` CHANGELOG](https://github.com/reactphp/async/blob/3.x/CHANGELOG.md) for more details.
## 2.0.0 (2022-07-11)
See [`2.x` CHANGELOG](https://github.com/reactphp/async/blob/2.x/CHANGELOG.md) for more details.
## 1.0.0 (2013-02-07)
* First tagged release

19
vendor/react/async/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2012 Christian Lück, Cees-Jan Kiewiet, Jan Sorgalla, Chris Boden, Igor Wiedler
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

672
vendor/react/async/README.md vendored Normal file
View File

@@ -0,0 +1,672 @@
# Async Utilities
[![CI status](https://github.com/reactphp/async/workflows/CI/badge.svg)](https://github.com/reactphp/async/actions)
[![installs on Packagist](https://img.shields.io/packagist/dt/react/async?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/react/async)
Async utilities and fibers for [ReactPHP](https://reactphp.org/).
This library allows you to manage async control flow. It provides a number of
combinators for [Promise](https://github.com/reactphp/promise)-based APIs.
Instead of nesting or chaining promise callbacks, you can declare them as a
list, which is resolved sequentially in an async manner.
React/Async will not automagically change blocking code to be async. You need
to have an actual event loop and non-blocking libraries interacting with that
event loop for it to work. As long as you have a Promise-based API that runs in
an event loop, it can be used with this library.
**Table of Contents**
* [Usage](#usage)
* [async()](#async)
* [await()](#await)
* [coroutine()](#coroutine)
* [delay()](#delay)
* [parallel()](#parallel)
* [series()](#series)
* [waterfall()](#waterfall)
* [Todo](#todo)
* [Install](#install)
* [Tests](#tests)
* [License](#license)
## Usage
This lightweight library consists only of a few simple functions.
All functions reside under the `React\Async` namespace.
The below examples refer to all functions with their fully-qualified names like this:
```php
React\Async\await();
```
As of PHP 5.6+ you can also import each required function into your code like this:
```php
use function React\Async\await;
await();
```
Alternatively, you can also use an import statement similar to this:
```php
use React\Async;
Async\await();
```
### async()
The `async(callable():(PromiseInterface<T>|T) $function): (callable():PromiseInterface<T>)` function can be used to
return an async function for a function that uses [`await()`](#await) internally.
This function is specifically designed to complement the [`await()` function](#await).
The [`await()` function](#await) can be considered *blocking* from the
perspective of the calling code. You can avoid this blocking behavior by
wrapping it in an `async()` function call. Everything inside this function
will still be blocked, but everything outside this function can be executed
asynchronously without blocking:
```php
Loop::addTimer(0.5, React\Async\async(function () {
echo 'a';
React\Async\await(React\Promise\Timer\sleep(1.0));
echo 'c';
}));
Loop::addTimer(1.0, function () {
echo 'b';
});
// prints "a" at t=0.5s
// prints "b" at t=1.0s
// prints "c" at t=1.5s
```
See also the [`await()` function](#await) for more details.
Note that this function only works in tandem with the [`await()` function](#await).
In particular, this function does not "magically" make any blocking function
non-blocking:
```php
Loop::addTimer(0.5, React\Async\async(function () {
echo 'a';
sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
echo 'c';
}));
Loop::addTimer(1.0, function () {
echo 'b';
});
// prints "a" at t=0.5s
// prints "c" at t=1.5s: Correct timing, but wrong order
// prints "b" at t=1.5s: Triggered too late because it was blocked
```
As an alternative, you should always make sure to use this function in tandem
with the [`await()` function](#await) and an async API returning a promise
as shown in the previous example.
The `async()` function is specifically designed for cases where it is used
as a callback (such as an event loop timer, event listener, or promise
callback). For this reason, it returns a new function wrapping the given
`$function` instead of directly invoking it and returning its value.
```php
use function React\Async\async;
Loop::addTimer(1.0, async(function () { }));
$connection->on('close', async(function () { }));
$stream->on('data', async(function ($data) { }));
$promise->then(async(function (int $result) { }));
```
You can invoke this wrapping function to invoke the given `$function` with
any arguments given as-is. The function will always return a Promise which
will be fulfilled with whatever your `$function` returns. Likewise, it will
return a promise that will be rejected if you throw an `Exception` or
`Throwable` from your `$function`. This allows you to easily create
Promise-based functions:
```php
$promise = React\Async\async(function (): int {
$browser = new React\Http\Browser();
$urls = [
'https://example.com/alice',
'https://example.com/bob'
];
$bytes = 0;
foreach ($urls as $url) {
$response = React\Async\await($browser->get($url));
assert($response instanceof Psr\Http\Message\ResponseInterface);
$bytes += $response->getBody()->getSize();
}
return $bytes;
})();
$promise->then(function (int $bytes) {
echo 'Total size: ' . $bytes . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
The previous example uses [`await()`](#await) inside a loop to highlight how
this vastly simplifies consuming asynchronous operations. At the same time,
this naive example does not leverage concurrent execution, as it will
essentially "await" between each operation. In order to take advantage of
concurrent execution within the given `$function`, you can "await" multiple
promises by using a single [`await()`](#await) together with Promise-based
primitives like this:
```php
$promise = React\Async\async(function (): int {
$browser = new React\Http\Browser();
$urls = [
'https://example.com/alice',
'https://example.com/bob'
];
$promises = [];
foreach ($urls as $url) {
$promises[] = $browser->get($url);
}
try {
$responses = React\Async\await(React\Promise\all($promises));
} catch (Exception $e) {
foreach ($promises as $promise) {
$promise->cancel();
}
throw $e;
}
$bytes = 0;
foreach ($responses as $response) {
assert($response instanceof Psr\Http\Message\ResponseInterface);
$bytes += $response->getBody()->getSize();
}
return $bytes;
})();
$promise->then(function (int $bytes) {
echo 'Total size: ' . $bytes . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
The returned promise is implemented in such a way that it can be cancelled
when it is still pending. Cancelling a pending promise will cancel any awaited
promises inside that fiber or any nested fibers. As such, the following example
will only output `ab` and cancel the pending [`delay()`](#delay).
The [`await()`](#await) calls in this example would throw a `RuntimeException`
from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
```php
$promise = async(static function (): int {
echo 'a';
await(async(static function (): void {
echo 'b';
delay(2);
echo 'c';
})());
echo 'd';
return time();
})();
$promise->cancel();
await($promise);
```
### await()
The `await(PromiseInterface<T> $promise): T` function can be used to
block waiting for the given `$promise` to be fulfilled.
```php
$result = React\Async\await($promise);
```
This function will only return after the given `$promise` has settled, i.e.
either fulfilled or rejected. While the promise is pending, this function
can be considered *blocking* from the perspective of the calling code.
You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
call. Everything inside this function will still be blocked, but everything
outside this function can be executed asynchronously without blocking:
```php
Loop::addTimer(0.5, React\Async\async(function () {
echo 'a';
React\Async\await(React\Promise\Timer\sleep(1.0));
echo 'c';
}));
Loop::addTimer(1.0, function () {
echo 'b';
});
// prints "a" at t=0.5s
// prints "b" at t=1.0s
// prints "c" at t=1.5s
```
See also the [`async()` function](#async) for more details.
Once the promise is fulfilled, this function will return whatever the promise
resolved to.
Once the promise is rejected, this will throw whatever the promise rejected
with. If the promise did not reject with an `Exception` or `Throwable`, then
this function will throw an `UnexpectedValueException` instead.
```php
try {
$result = React\Async\await($promise);
// promise successfully fulfilled with $result
echo 'Result: ' . $result;
} catch (Throwable $e) {
// promise rejected with $e
echo 'Error: ' . $e->getMessage();
}
```
### coroutine()
The `coroutine(callable(mixed ...$args):(\Generator|PromiseInterface<T>|T) $function, mixed ...$args): PromiseInterface<T>` function can be used to
execute a Generator-based coroutine to "await" promises.
```php
React\Async\coroutine(function () {
$browser = new React\Http\Browser();
try {
$response = yield $browser->get('https://example.com/');
assert($response instanceof Psr\Http\Message\ResponseInterface);
echo $response->getBody();
} catch (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
}
});
```
Using Generator-based coroutines is an alternative to directly using the
underlying promise APIs. For many use cases, this makes using promise-based
APIs much simpler, as it resembles a synchronous code flow more closely.
The above example performs the equivalent of directly using the promise APIs:
```php
$browser = new React\Http\Browser();
$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
echo $response->getBody();
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
The `yield` keyword can be used to "await" a promise resolution. Internally,
it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
This allows the execution to be interrupted and resumed at the same place
when the promise is fulfilled. The `yield` statement returns whatever the
promise is fulfilled with. If the promise is rejected, it will throw an
`Exception` or `Throwable`.
The `coroutine()` function will always return a Promise which will be
fulfilled with whatever your `$function` returns. Likewise, it will return
a promise that will be rejected if you throw an `Exception` or `Throwable`
from your `$function`. This allows you to easily create Promise-based
functions:
```php
$promise = React\Async\coroutine(function () {
$browser = new React\Http\Browser();
$urls = [
'https://example.com/alice',
'https://example.com/bob'
];
$bytes = 0;
foreach ($urls as $url) {
$response = yield $browser->get($url);
assert($response instanceof Psr\Http\Message\ResponseInterface);
$bytes += $response->getBody()->getSize();
}
return $bytes;
});
$promise->then(function (int $bytes) {
echo 'Total size: ' . $bytes . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
The previous example uses a `yield` statement inside a loop to highlight how
this vastly simplifies consuming asynchronous operations. At the same time,
this naive example does not leverage concurrent execution, as it will
essentially "await" between each operation. In order to take advantage of
concurrent execution within the given `$function`, you can "await" multiple
promises by using a single `yield` together with Promise-based primitives
like this:
```php
$promise = React\Async\coroutine(function () {
$browser = new React\Http\Browser();
$urls = [
'https://example.com/alice',
'https://example.com/bob'
];
$promises = [];
foreach ($urls as $url) {
$promises[] = $browser->get($url);
}
try {
$responses = yield React\Promise\all($promises);
} catch (Exception $e) {
foreach ($promises as $promise) {
$promise->cancel();
}
throw $e;
}
$bytes = 0;
foreach ($responses as $response) {
assert($response instanceof Psr\Http\Message\ResponseInterface);
$bytes += $response->getBody()->getSize();
}
return $bytes;
});
$promise->then(function (int $bytes) {
echo 'Total size: ' . $bytes . PHP_EOL;
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
### delay()
The `delay(float $seconds): void` function can be used to
delay program execution for duration given in `$seconds`.
```php
React\Async\delay($seconds);
```
This function will only return after the given number of `$seconds` have
elapsed. If there are no other events attached to this loop, it will behave
similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
```php
echo 'a';
React\Async\delay(1.0);
echo 'b';
// prints "a" at t=0.0s
// prints "b" at t=1.0s
```
Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
this function may not necessarily halt execution of the entire process thread.
Instead, it allows the event loop to run any other events attached to the
same loop until the delay returns:
```php
echo 'a';
Loop::addTimer(1.0, function (): void {
echo 'b';
});
React\Async\delay(3.0);
echo 'c';
// prints "a" at t=0.0s
// prints "b" at t=1.0s
// prints "c" at t=3.0s
```
This behavior is especially useful if you want to delay the program execution
of a particular routine, such as when building a simple polling or retry
mechanism:
```php
try {
something();
} catch (Throwable) {
// in case of error, retry after a short delay
React\Async\delay(1.0);
something();
}
```
Because this function only returns after some time has passed, it can be
considered *blocking* from the perspective of the calling code. You can avoid
this blocking behavior by wrapping it in an [`async()` function](#async) call.
Everything inside this function will still be blocked, but everything outside
this function can be executed asynchronously without blocking:
```php
Loop::addTimer(0.5, React\Async\async(function (): void {
echo 'a';
React\Async\delay(1.0);
echo 'c';
}));
Loop::addTimer(1.0, function (): void {
echo 'b';
});
// prints "a" at t=0.5s
// prints "b" at t=1.0s
// prints "c" at t=1.5s
```
See also the [`async()` function](#async) for more details.
Internally, the `$seconds` argument will be used as a timer for the loop so that
it keeps running until this timer triggers. This implies that if you pass a
really small (or negative) value, it will still start a timer and will thus
trigger at the earliest possible time in the future.
The function is implemented in such a way that it can be cancelled when it is
running inside an [`async()` function](#async). Cancelling the resulting
promise will clean up any pending timers and throw a `RuntimeException` from
the pending delay which in turn would reject the resulting promise.
```php
$promise = async(function (): void {
echo 'a';
delay(3.0);
echo 'b';
})();
Loop::addTimer(2.0, function () use ($promise): void {
$promise->cancel();
});
// prints "a" at t=0.0s
// rejects $promise at t=2.0
// never prints "b"
```
### parallel()
The `parallel(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
like this:
```php
<?php
use React\EventLoop\Loop;
use React\Promise\Promise;
React\Async\parallel([
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for a whole second');
});
});
},
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for another whole second');
});
});
},
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for yet another whole second');
});
});
},
])->then(function (array $results) {
foreach ($results as $result) {
var_dump($result);
}
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
### series()
The `series(iterable<callable():PromiseInterface<T>> $tasks): PromiseInterface<array<T>>` function can be used
like this:
```php
<?php
use React\EventLoop\Loop;
use React\Promise\Promise;
React\Async\series([
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for a whole second');
});
});
},
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for another whole second');
});
});
},
function () {
return new Promise(function ($resolve) {
Loop::addTimer(1, function () use ($resolve) {
$resolve('Slept for yet another whole second');
});
});
},
])->then(function (array $results) {
foreach ($results as $result) {
var_dump($result);
}
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
### waterfall()
The `waterfall(iterable<callable(mixed=):PromiseInterface<T>> $tasks): PromiseInterface<T>` function can be used
like this:
```php
<?php
use React\EventLoop\Loop;
use React\Promise\Promise;
$addOne = function ($prev = 0) {
return new Promise(function ($resolve) use ($prev) {
Loop::addTimer(1, function () use ($prev, $resolve) {
$resolve($prev + 1);
});
});
};
React\Async\waterfall([
$addOne,
$addOne,
$addOne
])->then(function ($prev) {
echo "Final result is $prev\n";
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```
## Todo
* Implement queue()
## Install
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:
```bash
composer require react/async:^4.3
```
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 8.1+.
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
[`3.x` branch](https://github.com/reactphp/async/tree/3.x) (PHP 7.1+) or
[`2.x` branch](https://github.com/reactphp/async/tree/2.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/async:^4 || ^3 || ^2"
```
## 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
```
## License
MIT, see [LICENSE file](LICENSE).
This project is heavily influenced by [async.js](https://github.com/caolan/async).

50
vendor/react/async/composer.json vendored Normal file
View File

@@ -0,0 +1,50 @@
{
"name": "react/async",
"description": "Async utilities and fibers for ReactPHP",
"keywords": ["async", "ReactPHP"],
"license": "MIT",
"authors": [
{
"name": "Christian Lück",
"homepage": "https://clue.engineering/",
"email": "christian@clue.engineering"
},
{
"name": "Cees-Jan Kiewiet",
"homepage": "https://wyrihaximus.net/",
"email": "reactphp@ceesjankiewiet.nl"
},
{
"name": "Jan Sorgalla",
"homepage": "https://sorgalla.com/",
"email": "jsorgalla@gmail.com"
},
{
"name": "Chris Boden",
"homepage": "https://cboden.dev/",
"email": "cboden@gmail.com"
}
],
"require": {
"php": ">=8.1",
"react/event-loop": "^1.2",
"react/promise": "^3.2 || ^2.8 || ^1.2.1"
},
"require-dev": {
"phpstan/phpstan": "1.10.39",
"phpunit/phpunit": "^9.6"
},
"autoload": {
"psr-4": {
"React\\Async\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"autoload-dev": {
"psr-4": {
"React\\Tests\\Async\\": "tests/"
}
}
}

33
vendor/react/async/src/FiberFactory.php vendored Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace React\Async;
/**
* This factory its only purpose is interoperability. Where with
* event loops one could simply wrap another event loop. But with fibers
* that has become impossible and as such we provide this factory and the
* FiberInterface.
*
* Usage is not documented and as such not supported and might chang without
* notice. Use at your own risk.
*
* @internal
*/
final class FiberFactory
{
private static ?\Closure $factory = null;
public static function create(): FiberInterface
{
return (self::factory())();
}
public static function factory(?\Closure $factory = null): \Closure
{
if ($factory !== null) {
self::$factory = $factory;
}
return self::$factory ?? static fn (): FiberInterface => new SimpleFiber();
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace React\Async;
/**
* This interface its only purpose is interoperability. Where with
* event loops one could simply wrap another event loop. But with fibers
* that has become impossible and as such we provide this interface and the
* FiberFactory.
*
* Usage is not documented and as such not supported and might chang without
* notice. Use at your own risk.
*
* @internal
*/
interface FiberInterface
{
public function resume(mixed $value): void;
public function throw(\Throwable $throwable): void;
public function suspend(): mixed;
}

42
vendor/react/async/src/FiberMap.php vendored Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace React\Async;
use React\Promise\PromiseInterface;
/**
* @internal
*
* @template T
*/
final class FiberMap
{
/** @var array<int,PromiseInterface<T>> */
private static array $map = [];
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
* @param PromiseInterface<T> $promise
*/
public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void
{
self::$map[\spl_object_id($fiber)] = $promise;
}
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
*/
public static function unsetPromise(\Fiber $fiber): void
{
unset(self::$map[\spl_object_id($fiber)]);
}
/**
* @param \Fiber<mixed,mixed,mixed,mixed> $fiber
* @return ?PromiseInterface<T>
*/
public static function getPromise(\Fiber $fiber): ?PromiseInterface
{
return self::$map[\spl_object_id($fiber)] ?? null;
}
}

79
vendor/react/async/src/SimpleFiber.php vendored Normal file
View File

@@ -0,0 +1,79 @@
<?php
namespace React\Async;
use React\EventLoop\Loop;
/**
* @internal
*/
final class SimpleFiber implements FiberInterface
{
/** @var ?\Fiber<void,void,void,callable(): mixed> */
private static ?\Fiber $scheduler = null;
private static ?\Closure $suspend = null;
/** @var ?\Fiber<mixed,mixed,mixed,mixed> */
private ?\Fiber $fiber = null;
public function __construct()
{
$this->fiber = \Fiber::getCurrent();
}
public function resume(mixed $value): void
{
if ($this->fiber !== null) {
$this->fiber->resume($value);
} else {
self::$suspend = static fn() => $value;
}
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
$suspend = self::$suspend;
self::$suspend = null;
\Fiber::suspend($suspend);
}
}
public function throw(\Throwable $throwable): void
{
if ($this->fiber !== null) {
$this->fiber->throw($throwable);
} else {
self::$suspend = static fn() => throw $throwable;
}
if (self::$suspend !== null && \Fiber::getCurrent() === self::$scheduler) {
$suspend = self::$suspend;
self::$suspend = null;
\Fiber::suspend($suspend);
}
}
public function suspend(): mixed
{
if ($this->fiber === null) {
if (self::$scheduler === null || self::$scheduler->isTerminated()) {
self::$scheduler = new \Fiber(static fn() => Loop::run());
// Run event loop to completion on shutdown.
\register_shutdown_function(static function (): void {
assert(self::$scheduler instanceof \Fiber);
if (self::$scheduler->isSuspended()) {
self::$scheduler->resume();
}
});
}
$ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start());
assert(\is_callable($ret));
return $ret();
}
return \Fiber::suspend();
}
}

846
vendor/react/async/src/functions.php vendored Normal file
View File

@@ -0,0 +1,846 @@
<?php
namespace React\Async;
use React\EventLoop\Loop;
use React\EventLoop\TimerInterface;
use React\Promise\Deferred;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
use function React\Promise\reject;
use function React\Promise\resolve;
/**
* Return an async function for a function that uses [`await()`](#await) internally.
*
* This function is specifically designed to complement the [`await()` function](#await).
* The [`await()` function](#await) can be considered *blocking* from the
* perspective of the calling code. You can avoid this blocking behavior by
* wrapping it in an `async()` function call. Everything inside this function
* will still be blocked, but everything outside this function can be executed
* asynchronously without blocking:
*
* ```php
* Loop::addTimer(0.5, React\Async\async(function () {
* echo 'a';
* React\Async\await(React\Promise\Timer\sleep(1.0));
* echo 'c';
* }));
*
* Loop::addTimer(1.0, function () {
* echo 'b';
* });
*
* // prints "a" at t=0.5s
* // prints "b" at t=1.0s
* // prints "c" at t=1.5s
* ```
*
* See also the [`await()` function](#await) for more details.
*
* Note that this function only works in tandem with the [`await()` function](#await).
* In particular, this function does not "magically" make any blocking function
* non-blocking:
*
* ```php
* Loop::addTimer(0.5, React\Async\async(function () {
* echo 'a';
* sleep(1); // broken: using PHP's blocking sleep() for demonstration purposes
* echo 'c';
* }));
*
* Loop::addTimer(1.0, function () {
* echo 'b';
* });
*
* // prints "a" at t=0.5s
* // prints "c" at t=1.5s: Correct timing, but wrong order
* // prints "b" at t=1.5s: Triggered too late because it was blocked
* ```
*
* As an alternative, you should always make sure to use this function in tandem
* with the [`await()` function](#await) and an async API returning a promise
* as shown in the previous example.
*
* The `async()` function is specifically designed for cases where it is used
* as a callback (such as an event loop timer, event listener, or promise
* callback). For this reason, it returns a new function wrapping the given
* `$function` instead of directly invoking it and returning its value.
*
* ```php
* use function React\Async\async;
*
* Loop::addTimer(1.0, async(function () { … }));
* $connection->on('close', async(function () { … }));
* $stream->on('data', async(function ($data) { … }));
* $promise->then(async(function (int $result) { … }));
* ```
*
* You can invoke this wrapping function to invoke the given `$function` with
* any arguments given as-is. The function will always return a Promise which
* will be fulfilled with whatever your `$function` returns. Likewise, it will
* return a promise that will be rejected if you throw an `Exception` or
* `Throwable` from your `$function`. This allows you to easily create
* Promise-based functions:
*
* ```php
* $promise = React\Async\async(function (): int {
* $browser = new React\Http\Browser();
* $urls = [
* 'https://example.com/alice',
* 'https://example.com/bob'
* ];
*
* $bytes = 0;
* foreach ($urls as $url) {
* $response = React\Async\await($browser->get($url));
* assert($response instanceof Psr\Http\Message\ResponseInterface);
* $bytes += $response->getBody()->getSize();
* }
* return $bytes;
* })();
*
* $promise->then(function (int $bytes) {
* echo 'Total size: ' . $bytes . PHP_EOL;
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* The previous example uses [`await()`](#await) inside a loop to highlight how
* this vastly simplifies consuming asynchronous operations. At the same time,
* this naive example does not leverage concurrent execution, as it will
* essentially "await" between each operation. In order to take advantage of
* concurrent execution within the given `$function`, you can "await" multiple
* promises by using a single [`await()`](#await) together with Promise-based
* primitives like this:
*
* ```php
* $promise = React\Async\async(function (): int {
* $browser = new React\Http\Browser();
* $urls = [
* 'https://example.com/alice',
* 'https://example.com/bob'
* ];
*
* $promises = [];
* foreach ($urls as $url) {
* $promises[] = $browser->get($url);
* }
*
* try {
* $responses = React\Async\await(React\Promise\all($promises));
* } catch (Exception $e) {
* foreach ($promises as $promise) {
* $promise->cancel();
* }
* throw $e;
* }
*
* $bytes = 0;
* foreach ($responses as $response) {
* assert($response instanceof Psr\Http\Message\ResponseInterface);
* $bytes += $response->getBody()->getSize();
* }
* return $bytes;
* })();
*
* $promise->then(function (int $bytes) {
* echo 'Total size: ' . $bytes . PHP_EOL;
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* The returned promise is implemented in such a way that it can be cancelled
* when it is still pending. Cancelling a pending promise will cancel any awaited
* promises inside that fiber or any nested fibers. As such, the following example
* will only output `ab` and cancel the pending [`delay()`](#delay).
* The [`await()`](#await) calls in this example would throw a `RuntimeException`
* from the cancelled [`delay()`](#delay) call that bubbles up through the fibers.
*
* ```php
* $promise = async(static function (): int {
* echo 'a';
* await(async(static function (): void {
* echo 'b';
* delay(2);
* echo 'c';
* })());
* echo 'd';
*
* return time();
* })();
*
* $promise->cancel();
* await($promise);
* ```
*
* @template T
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
* @template A2
* @template A3
* @template A4
* @template A5
* @param callable(A1,A2,A3,A4,A5): (PromiseInterface<T>|T) $function
* @return callable(A1=,A2=,A3=,A4=,A5=): PromiseInterface<T>
* @since 4.0.0
* @see coroutine()
*/
function async(callable $function): callable
{
return static function (mixed ...$args) use ($function): PromiseInterface {
$fiber = null;
/** @var PromiseInterface<T> $promise*/
$promise = new Promise(function (callable $resolve, callable $reject) use ($function, $args, &$fiber): void {
$fiber = new \Fiber(function () use ($resolve, $reject, $function, $args, &$fiber): void {
try {
$resolve($function(...$args));
} catch (\Throwable $exception) {
$reject($exception);
} finally {
assert($fiber instanceof \Fiber);
FiberMap::unsetPromise($fiber);
}
});
$fiber->start();
}, function () use (&$fiber): void {
assert($fiber instanceof \Fiber);
$promise = FiberMap::getPromise($fiber);
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
});
$lowLevelFiber = \Fiber::getCurrent();
if ($lowLevelFiber !== null) {
FiberMap::setPromise($lowLevelFiber, $promise);
}
return $promise;
};
}
/**
* Block waiting for the given `$promise` to be fulfilled.
*
* ```php
* $result = React\Async\await($promise);
* ```
*
* This function will only return after the given `$promise` has settled, i.e.
* either fulfilled or rejected. While the promise is pending, this function
* can be considered *blocking* from the perspective of the calling code.
* You can avoid this blocking behavior by wrapping it in an [`async()` function](#async)
* call. Everything inside this function will still be blocked, but everything
* outside this function can be executed asynchronously without blocking:
*
* ```php
* Loop::addTimer(0.5, React\Async\async(function () {
* echo 'a';
* React\Async\await(React\Promise\Timer\sleep(1.0));
* echo 'c';
* }));
*
* Loop::addTimer(1.0, function () {
* echo 'b';
* });
*
* // prints "a" at t=0.5s
* // prints "b" at t=1.0s
* // prints "c" at t=1.5s
* ```
*
* See also the [`async()` function](#async) for more details.
*
* Once the promise is fulfilled, this function will return whatever the promise
* resolved to.
*
* Once the promise is rejected, this will throw whatever the promise rejected
* with. If the promise did not reject with an `Exception` or `Throwable`, then
* this function will throw an `UnexpectedValueException` instead.
*
* ```php
* try {
* $result = React\Async\await($promise);
* // promise successfully fulfilled with $result
* echo 'Result: ' . $result;
* } catch (Throwable $e) {
* // promise rejected with $e
* echo 'Error: ' . $e->getMessage();
* }
* ```
*
* @template T
* @param PromiseInterface<T> $promise
* @return T returns whatever the promise resolves to
* @throws \Exception when the promise is rejected with an `Exception`
* @throws \Throwable when the promise is rejected with a `Throwable`
* @throws \UnexpectedValueException when the promise is rejected with an unexpected value (Promise API v1 or v2 only)
*/
function await(PromiseInterface $promise): mixed
{
$fiber = null;
$resolved = false;
$rejected = false;
/** @var T $resolvedValue */
$resolvedValue = null;
$rejectedThrowable = null;
$lowLevelFiber = \Fiber::getCurrent();
$promise->then(
function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFiber): void {
if ($lowLevelFiber !== null) {
FiberMap::unsetPromise($lowLevelFiber);
}
/** @var ?\Fiber<mixed,mixed,mixed,mixed> $fiber */
if ($fiber === null) {
$resolved = true;
/** @var T $resolvedValue */
$resolvedValue = $value;
return;
}
$fiber->resume($value);
},
function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowLevelFiber): void {
if ($lowLevelFiber !== null) {
FiberMap::unsetPromise($lowLevelFiber);
}
if (!$throwable instanceof \Throwable) {
$throwable = new \UnexpectedValueException(
'Promise rejected with unexpected value of type ' . (is_object($throwable) ? get_class($throwable) : gettype($throwable)) /** @phpstan-ignore-line */
);
// avoid garbage references by replacing all closures in call stack.
// what a lovely piece of code!
$r = new \ReflectionProperty('Exception', 'trace');
$trace = $r->getValue($throwable);
assert(\is_array($trace));
// Exception trace arguments only available when zend.exception_ignore_args is not set
// @codeCoverageIgnoreStart
foreach ($trace as $ti => $one) {
if (isset($one['args'])) {
foreach ($one['args'] as $ai => $arg) {
if ($arg instanceof \Closure) {
$trace[$ti]['args'][$ai] = 'Object(' . \get_class($arg) . ')';
}
}
}
}
// @codeCoverageIgnoreEnd
$r->setValue($throwable, $trace);
}
if ($fiber === null) {
$rejected = true;
$rejectedThrowable = $throwable;
return;
}
$fiber->throw($throwable);
}
);
if ($resolved) {
return $resolvedValue;
}
if ($rejected) {
assert($rejectedThrowable instanceof \Throwable);
throw $rejectedThrowable;
}
if ($lowLevelFiber !== null) {
FiberMap::setPromise($lowLevelFiber, $promise);
}
$fiber = FiberFactory::create();
return $fiber->suspend();
}
/**
* Delay program execution for duration given in `$seconds`.
*
* ```php
* React\Async\delay($seconds);
* ```
*
* This function will only return after the given number of `$seconds` have
* elapsed. If there are no other events attached to this loop, it will behave
* similar to PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php).
*
* ```php
* echo 'a';
* React\Async\delay(1.0);
* echo 'b';
*
* // prints "a" at t=0.0s
* // prints "b" at t=1.0s
* ```
*
* Unlike PHP's [`sleep()` function](https://www.php.net/manual/en/function.sleep.php),
* this function may not necessarily halt execution of the entire process thread.
* Instead, it allows the event loop to run any other events attached to the
* same loop until the delay returns:
*
* ```php
* echo 'a';
* Loop::addTimer(1.0, function (): void {
* echo 'b';
* });
* React\Async\delay(3.0);
* echo 'c';
*
* // prints "a" at t=0.0s
* // prints "b" at t=1.0s
* // prints "c" at t=3.0s
* ```
*
* This behavior is especially useful if you want to delay the program execution
* of a particular routine, such as when building a simple polling or retry
* mechanism:
*
* ```php
* try {
* something();
* } catch (Throwable) {
* // in case of error, retry after a short delay
* React\Async\delay(1.0);
* something();
* }
* ```
*
* Because this function only returns after some time has passed, it can be
* considered *blocking* from the perspective of the calling code. You can avoid
* this blocking behavior by wrapping it in an [`async()` function](#async) call.
* Everything inside this function will still be blocked, but everything outside
* this function can be executed asynchronously without blocking:
*
* ```php
* Loop::addTimer(0.5, React\Async\async(function (): void {
* echo 'a';
* React\Async\delay(1.0);
* echo 'c';
* }));
*
* Loop::addTimer(1.0, function (): void {
* echo 'b';
* });
*
* // prints "a" at t=0.5s
* // prints "b" at t=1.0s
* // prints "c" at t=1.5s
* ```
*
* See also the [`async()` function](#async) for more details.
*
* Internally, the `$seconds` argument will be used as a timer for the loop so that
* it keeps running until this timer triggers. This implies that if you pass a
* really small (or negative) value, it will still start a timer and will thus
* trigger at the earliest possible time in the future.
*
* The function is implemented in such a way that it can be cancelled when it is
* running inside an [`async()` function](#async). Cancelling the resulting
* promise will clean up any pending timers and throw a `RuntimeException` from
* the pending delay which in turn would reject the resulting promise.
*
* ```php
* $promise = async(function (): void {
* echo 'a';
* delay(3.0);
* echo 'b';
* })();
*
* Loop::addTimer(2.0, function () use ($promise): void {
* $promise->cancel();
* });
*
* // prints "a" at t=0.0s
* // rejects $promise at t=2.0
* // never prints "b"
* ```
*
* @return void
* @throws \RuntimeException when the function is cancelled inside an `async()` function
* @see async()
* @uses await()
*/
function delay(float $seconds): void
{
/** @var ?TimerInterface $timer */
$timer = null;
await(new Promise(function (callable $resolve) use ($seconds, &$timer): void {
$timer = Loop::addTimer($seconds, fn() => $resolve(null));
}, function () use (&$timer): void {
assert($timer instanceof TimerInterface);
Loop::cancelTimer($timer);
throw new \RuntimeException('Delay cancelled');
}));
}
/**
* Execute a Generator-based coroutine to "await" promises.
*
* ```php
* React\Async\coroutine(function () {
* $browser = new React\Http\Browser();
*
* try {
* $response = yield $browser->get('https://example.com/');
* assert($response instanceof Psr\Http\Message\ResponseInterface);
* echo $response->getBody();
* } catch (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* }
* });
* ```
*
* Using Generator-based coroutines is an alternative to directly using the
* underlying promise APIs. For many use cases, this makes using promise-based
* APIs much simpler, as it resembles a synchronous code flow more closely.
* The above example performs the equivalent of directly using the promise APIs:
*
* ```php
* $browser = new React\Http\Browser();
*
* $browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
* echo $response->getBody();
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* The `yield` keyword can be used to "await" a promise resolution. Internally,
* it will turn the entire given `$function` into a [`Generator`](https://www.php.net/manual/en/class.generator.php).
* This allows the execution to be interrupted and resumed at the same place
* when the promise is fulfilled. The `yield` statement returns whatever the
* promise is fulfilled with. If the promise is rejected, it will throw an
* `Exception` or `Throwable`.
*
* The `coroutine()` function will always return a Promise which will be
* fulfilled with whatever your `$function` returns. Likewise, it will return
* a promise that will be rejected if you throw an `Exception` or `Throwable`
* from your `$function`. This allows you to easily create Promise-based
* functions:
*
* ```php
* $promise = React\Async\coroutine(function () {
* $browser = new React\Http\Browser();
* $urls = [
* 'https://example.com/alice',
* 'https://example.com/bob'
* ];
*
* $bytes = 0;
* foreach ($urls as $url) {
* $response = yield $browser->get($url);
* assert($response instanceof Psr\Http\Message\ResponseInterface);
* $bytes += $response->getBody()->getSize();
* }
* return $bytes;
* });
*
* $promise->then(function (int $bytes) {
* echo 'Total size: ' . $bytes . PHP_EOL;
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* The previous example uses a `yield` statement inside a loop to highlight how
* this vastly simplifies consuming asynchronous operations. At the same time,
* this naive example does not leverage concurrent execution, as it will
* essentially "await" between each operation. In order to take advantage of
* concurrent execution within the given `$function`, you can "await" multiple
* promises by using a single `yield` together with Promise-based primitives
* like this:
*
* ```php
* $promise = React\Async\coroutine(function () {
* $browser = new React\Http\Browser();
* $urls = [
* 'https://example.com/alice',
* 'https://example.com/bob'
* ];
*
* $promises = [];
* foreach ($urls as $url) {
* $promises[] = $browser->get($url);
* }
*
* try {
* $responses = yield React\Promise\all($promises);
* } catch (Exception $e) {
* foreach ($promises as $promise) {
* $promise->cancel();
* }
* throw $e;
* }
*
* $bytes = 0;
* foreach ($responses as $response) {
* assert($response instanceof Psr\Http\Message\ResponseInterface);
* $bytes += $response->getBody()->getSize();
* }
* return $bytes;
* });
*
* $promise->then(function (int $bytes) {
* echo 'Total size: ' . $bytes . PHP_EOL;
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* @template T
* @template TYield
* @template A1 (any number of function arguments, see https://github.com/phpstan/phpstan/issues/8214)
* @template A2
* @template A3
* @template A4
* @template A5
* @param callable(A1, A2, A3, A4, A5):(\Generator<mixed, PromiseInterface<TYield>, TYield, PromiseInterface<T>|T>|PromiseInterface<T>|T) $function
* @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is
* @return PromiseInterface<T>
* @since 3.0.0
*/
function coroutine(callable $function, mixed ...$args): PromiseInterface
{
try {
$generator = $function(...$args);
} catch (\Throwable $e) {
return reject($e);
}
if (!$generator instanceof \Generator) {
return resolve($generator);
}
$promise = null;
/** @var Deferred<T> $deferred*/
$deferred = new Deferred(function () use (&$promise) {
/** @var ?PromiseInterface<T> $promise */
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
$promise = null;
});
/** @var callable $next */
$next = function () use ($deferred, $generator, &$next, &$promise) {
try {
if (!$generator->valid()) {
$next = null;
$deferred->resolve($generator->getReturn());
return;
}
} catch (\Throwable $e) {
$next = null;
$deferred->reject($e);
return;
}
$promise = $generator->current();
if (!$promise instanceof PromiseInterface) {
$next = null;
$deferred->reject(new \UnexpectedValueException(
'Expected coroutine to yield ' . PromiseInterface::class . ', but got ' . (is_object($promise) ? get_class($promise) : gettype($promise))
));
return;
}
/** @var PromiseInterface<TYield> $promise */
assert($next instanceof \Closure);
$promise->then(function ($value) use ($generator, $next) {
$generator->send($value);
$next();
}, function (\Throwable $reason) use ($generator, $next) {
$generator->throw($reason);
$next();
})->then(null, function (\Throwable $reason) use ($deferred, &$next) {
$next = null;
$deferred->reject($reason);
});
};
$next();
return $deferred->promise();
}
/**
* @template T
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
* @return PromiseInterface<array<T>>
*/
function parallel(iterable $tasks): PromiseInterface
{
/** @var array<int,PromiseInterface<T>> $pending */
$pending = [];
/** @var Deferred<array<T>> $deferred */
$deferred = new Deferred(function () use (&$pending) {
foreach ($pending as $promise) {
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
}
$pending = [];
});
$results = [];
$continue = true;
$taskErrback = function ($error) use (&$pending, $deferred, &$continue) {
$continue = false;
$deferred->reject($error);
foreach ($pending as $promise) {
if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) {
$promise->cancel();
}
}
$pending = [];
};
foreach ($tasks as $i => $task) {
$taskCallback = function ($result) use (&$results, &$pending, &$continue, $i, $deferred) {
$results[$i] = $result;
unset($pending[$i]);
if (!$pending && !$continue) {
$deferred->resolve($results);
}
};
$promise = \call_user_func($task);
assert($promise instanceof PromiseInterface);
$pending[$i] = $promise;
$promise->then($taskCallback, $taskErrback);
if (!$continue) {
break;
}
}
$continue = false;
if (!$pending) {
$deferred->resolve($results);
}
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
return $deferred->promise();
}
/**
* @template T
* @param iterable<callable():(PromiseInterface<T>|T)> $tasks
* @return PromiseInterface<array<T>>
*/
function series(iterable $tasks): PromiseInterface
{
$pending = null;
/** @var Deferred<array<T>> $deferred */
$deferred = new Deferred(function () use (&$pending) {
/** @var ?PromiseInterface<T> $pending */
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
$pending->cancel();
}
$pending = null;
});
$results = [];
if ($tasks instanceof \IteratorAggregate) {
$tasks = $tasks->getIterator();
assert($tasks instanceof \Iterator);
}
$taskCallback = function ($result) use (&$results, &$next) {
$results[] = $result;
/** @var \Closure $next */
$next();
};
$next = function () use (&$tasks, $taskCallback, $deferred, &$results, &$pending) {
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
$deferred->resolve($results);
return;
}
if ($tasks instanceof \Iterator) {
$task = $tasks->current();
$tasks->next();
} else {
assert(\is_array($tasks));
$task = \array_shift($tasks);
}
assert(\is_callable($task));
$promise = \call_user_func($task);
assert($promise instanceof PromiseInterface);
$pending = $promise;
$promise->then($taskCallback, array($deferred, 'reject'));
};
$next();
/** @var PromiseInterface<array<T>> Remove once defining `Deferred()` above is supported by PHPStan, see https://github.com/phpstan/phpstan/issues/11032 */
return $deferred->promise();
}
/**
* @template T
* @param iterable<(callable():(PromiseInterface<T>|T))|(callable(mixed):(PromiseInterface<T>|T))> $tasks
* @return PromiseInterface<($tasks is non-empty-array|\Traversable ? T : null)>
*/
function waterfall(iterable $tasks): PromiseInterface
{
$pending = null;
/** @var Deferred<T> $deferred*/
$deferred = new Deferred(function () use (&$pending) {
/** @var ?PromiseInterface<T> $pending */
if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) {
$pending->cancel();
}
$pending = null;
});
if ($tasks instanceof \IteratorAggregate) {
$tasks = $tasks->getIterator();
assert($tasks instanceof \Iterator);
}
/** @var callable $next */
$next = function ($value = null) use (&$tasks, &$next, $deferred, &$pending) {
if ($tasks instanceof \Iterator ? !$tasks->valid() : !$tasks) {
$deferred->resolve($value);
return;
}
if ($tasks instanceof \Iterator) {
$task = $tasks->current();
$tasks->next();
} else {
assert(\is_array($tasks));
$task = \array_shift($tasks);
}
assert(\is_callable($task));
$promise = \call_user_func_array($task, func_get_args());
assert($promise instanceof PromiseInterface);
$pending = $promise;
$promise->then($next, array($deferred, 'reject'));
};
$next();
return $deferred->promise();
}

View File

@@ -0,0 +1,9 @@
<?php
namespace React\Async;
// @codeCoverageIgnoreStart
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
require __DIR__ . '/functions.php';
}
// @codeCoverageIgnoreEnd