+
This commit is contained in:
112
vendor/react/async/CHANGELOG.md
vendored
Normal file
112
vendor/react/async/CHANGELOG.md
vendored
Normal 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
19
vendor/react/async/LICENSE
vendored
Normal 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
672
vendor/react/async/README.md
vendored
Normal file
@@ -0,0 +1,672 @@
|
||||
# Async Utilities
|
||||
|
||||
[](https://github.com/reactphp/async/actions)
|
||||
[](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
50
vendor/react/async/composer.json
vendored
Normal 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
33
vendor/react/async/src/FiberFactory.php
vendored
Normal 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();
|
||||
}
|
||||
}
|
||||
23
vendor/react/async/src/FiberInterface.php
vendored
Normal file
23
vendor/react/async/src/FiberInterface.php
vendored
Normal 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
42
vendor/react/async/src/FiberMap.php
vendored
Normal 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
79
vendor/react/async/src/SimpleFiber.php
vendored
Normal 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
846
vendor/react/async/src/functions.php
vendored
Normal 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();
|
||||
}
|
||||
9
vendor/react/async/src/functions_include.php
vendored
Normal file
9
vendor/react/async/src/functions_include.php
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace React\Async;
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
if (!\function_exists(__NAMESPACE__ . '\\parallel')) {
|
||||
require __DIR__ . '/functions.php';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
Reference in New Issue
Block a user