暂存
This commit is contained in:
+345
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
namespace React\Promise;
|
||||
|
||||
use React\Promise\Exception\CompositeException;
|
||||
use React\Promise\Internal\FulfilledPromise;
|
||||
use React\Promise\Internal\RejectedPromise;
|
||||
|
||||
/**
|
||||
* Creates a promise for the supplied `$promiseOrValue`.
|
||||
*
|
||||
* If `$promiseOrValue` is a value, it will be the resolution value of the
|
||||
* returned promise.
|
||||
*
|
||||
* If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
|
||||
* a trusted promise that follows the state of the thenable is returned.
|
||||
*
|
||||
* If `$promiseOrValue` is a promise, it will be returned as is.
|
||||
*
|
||||
* @template T
|
||||
* @param PromiseInterface<T>|T $promiseOrValue
|
||||
* @return PromiseInterface<T>
|
||||
*/
|
||||
function resolve($promiseOrValue): PromiseInterface
|
||||
{
|
||||
if ($promiseOrValue instanceof PromiseInterface) {
|
||||
return $promiseOrValue;
|
||||
}
|
||||
|
||||
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
|
||||
$canceller = null;
|
||||
|
||||
if (\method_exists($promiseOrValue, 'cancel')) {
|
||||
$canceller = [$promiseOrValue, 'cancel'];
|
||||
assert(\is_callable($canceller));
|
||||
}
|
||||
|
||||
/** @var Promise<T> */
|
||||
return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void {
|
||||
$promiseOrValue->then($resolve, $reject);
|
||||
}, $canceller);
|
||||
}
|
||||
|
||||
return new FulfilledPromise($promiseOrValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rejected promise for the supplied `$reason`.
|
||||
*
|
||||
* If `$reason` is a value, it will be the rejection value of the
|
||||
* returned promise.
|
||||
*
|
||||
* If `$reason` is a promise, its completion value will be the rejected
|
||||
* value of the returned promise.
|
||||
*
|
||||
* This can be useful in situations where you need to reject a promise without
|
||||
* throwing an exception. For example, it allows you to propagate a rejection with
|
||||
* the value of another promise.
|
||||
*
|
||||
* @return PromiseInterface<never>
|
||||
*/
|
||||
function reject(\Throwable $reason): PromiseInterface
|
||||
{
|
||||
return new RejectedPromise($reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that will resolve only once all the items in
|
||||
* `$promisesOrValues` have resolved. The resolution value of the returned promise
|
||||
* will be an array containing the resolution values of each of the items in
|
||||
* `$promisesOrValues`.
|
||||
*
|
||||
* @template T
|
||||
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
|
||||
* @return PromiseInterface<array<T>>
|
||||
*/
|
||||
function all(iterable $promisesOrValues): PromiseInterface
|
||||
{
|
||||
$cancellationQueue = new Internal\CancellationQueue();
|
||||
|
||||
/** @var Promise<array<T>> */
|
||||
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
|
||||
$toResolve = 0;
|
||||
/** @var bool */
|
||||
$continue = true;
|
||||
$values = [];
|
||||
|
||||
foreach ($promisesOrValues as $i => $promiseOrValue) {
|
||||
$cancellationQueue->enqueue($promiseOrValue);
|
||||
$values[$i] = null;
|
||||
++$toResolve;
|
||||
|
||||
resolve($promiseOrValue)->then(
|
||||
function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
|
||||
$values[$i] = $value;
|
||||
|
||||
if (0 === --$toResolve && !$continue) {
|
||||
$resolve($values);
|
||||
}
|
||||
},
|
||||
function (\Throwable $reason) use (&$continue, $reject): void {
|
||||
$continue = false;
|
||||
$reject($reason);
|
||||
}
|
||||
);
|
||||
|
||||
if (!$continue && !\is_array($promisesOrValues)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$continue = false;
|
||||
if ($toResolve === 0) {
|
||||
$resolve($values);
|
||||
}
|
||||
}, $cancellationQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a competitive race that allows one winner. Returns a promise which is
|
||||
* resolved in the same way the first settled promise resolves.
|
||||
*
|
||||
* The returned promise will become **infinitely pending** if `$promisesOrValues`
|
||||
* contains 0 items.
|
||||
*
|
||||
* @template T
|
||||
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
|
||||
* @return PromiseInterface<T>
|
||||
*/
|
||||
function race(iterable $promisesOrValues): PromiseInterface
|
||||
{
|
||||
$cancellationQueue = new Internal\CancellationQueue();
|
||||
|
||||
/** @var Promise<T> */
|
||||
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
|
||||
$continue = true;
|
||||
|
||||
foreach ($promisesOrValues as $promiseOrValue) {
|
||||
$cancellationQueue->enqueue($promiseOrValue);
|
||||
|
||||
resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
|
||||
$continue = false;
|
||||
});
|
||||
|
||||
if (!$continue && !\is_array($promisesOrValues)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, $cancellationQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that will resolve when any one of the items in
|
||||
* `$promisesOrValues` resolves. The resolution value of the returned promise
|
||||
* will be the resolution value of the triggering item.
|
||||
*
|
||||
* The returned promise will only reject if *all* items in `$promisesOrValues` are
|
||||
* rejected. The rejection value will be an array of all rejection reasons.
|
||||
*
|
||||
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
|
||||
* if `$promisesOrValues` contains 0 items.
|
||||
*
|
||||
* @template T
|
||||
* @param iterable<PromiseInterface<T>|T> $promisesOrValues
|
||||
* @return PromiseInterface<T>
|
||||
*/
|
||||
function any(iterable $promisesOrValues): PromiseInterface
|
||||
{
|
||||
$cancellationQueue = new Internal\CancellationQueue();
|
||||
|
||||
/** @var Promise<T> */
|
||||
return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
|
||||
$toReject = 0;
|
||||
$continue = true;
|
||||
$reasons = [];
|
||||
|
||||
foreach ($promisesOrValues as $i => $promiseOrValue) {
|
||||
$cancellationQueue->enqueue($promiseOrValue);
|
||||
++$toReject;
|
||||
|
||||
resolve($promiseOrValue)->then(
|
||||
function ($value) use ($resolve, &$continue): void {
|
||||
$continue = false;
|
||||
$resolve($value);
|
||||
},
|
||||
function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
|
||||
$reasons[$i] = $reason;
|
||||
|
||||
if (0 === --$toReject && !$continue) {
|
||||
$reject(new CompositeException(
|
||||
$reasons,
|
||||
'All promises rejected.'
|
||||
));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!$continue && !\is_array($promisesOrValues)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$continue = false;
|
||||
if ($toReject === 0 && !$reasons) {
|
||||
$reject(new Exception\LengthException(
|
||||
'Must contain at least 1 item but contains only 0 items.'
|
||||
));
|
||||
} elseif ($toReject === 0) {
|
||||
$reject(new CompositeException(
|
||||
$reasons,
|
||||
'All promises rejected.'
|
||||
));
|
||||
}
|
||||
}, $cancellationQueue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global rejection handler for unhandled promise rejections.
|
||||
*
|
||||
* Note that rejected promises should always be handled similar to how any
|
||||
* exceptions should always be caught in a `try` + `catch` block. If you remove
|
||||
* the last reference to a rejected promise that has not been handled, it will
|
||||
* report an unhandled promise rejection. See also the [`reject()` function](#reject)
|
||||
* for more details.
|
||||
*
|
||||
* The `?callable $callback` argument MUST be a valid callback function that
|
||||
* accepts a single `Throwable` argument or a `null` value to restore the
|
||||
* default promise rejection handler. The return value of the callback function
|
||||
* will be ignored and has no effect, so you SHOULD return a `void` value. The
|
||||
* callback function MUST NOT throw or the program will be terminated with a
|
||||
* fatal error.
|
||||
*
|
||||
* The function returns the previous rejection handler or `null` if using the
|
||||
* default promise rejection handler.
|
||||
*
|
||||
* The default promise rejection handler will log an error message plus its
|
||||
* stack trace:
|
||||
*
|
||||
* ```php
|
||||
* // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
|
||||
* React\Promise\reject(new RuntimeException('Unhandled'));
|
||||
* ```
|
||||
*
|
||||
* The promise rejection handler may be used to use customize the log message or
|
||||
* write to custom log targets. As a rule of thumb, this function should only be
|
||||
* used as a last resort and promise rejections are best handled with either the
|
||||
* [`then()` method](#promiseinterfacethen), the
|
||||
* [`catch()` method](#promiseinterfacecatch), or the
|
||||
* [`finally()` method](#promiseinterfacefinally).
|
||||
* See also the [`reject()` function](#reject) for more details.
|
||||
*
|
||||
* @param callable(\Throwable):void|null $callback
|
||||
* @return callable(\Throwable):void|null
|
||||
*/
|
||||
function set_rejection_handler(?callable $callback): ?callable
|
||||
{
|
||||
static $current = null;
|
||||
$previous = $current;
|
||||
$current = $callback;
|
||||
|
||||
return $previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
function _checkTypehint(callable $callback, \Throwable $reason): bool
|
||||
{
|
||||
if (\is_array($callback)) {
|
||||
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
|
||||
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
|
||||
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
|
||||
} else {
|
||||
assert($callback instanceof \Closure || \is_string($callback));
|
||||
$callbackReflection = new \ReflectionFunction($callback);
|
||||
}
|
||||
|
||||
$parameters = $callbackReflection->getParameters();
|
||||
|
||||
if (!isset($parameters[0])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$expectedException = $parameters[0];
|
||||
|
||||
// Extract the type of the argument and handle different possibilities
|
||||
$type = $expectedException->getType();
|
||||
|
||||
$isTypeUnion = true;
|
||||
$types = [];
|
||||
|
||||
switch (true) {
|
||||
case $type === null:
|
||||
break;
|
||||
case $type instanceof \ReflectionNamedType:
|
||||
$types = [$type];
|
||||
break;
|
||||
case $type instanceof \ReflectionIntersectionType:
|
||||
$isTypeUnion = false;
|
||||
case $type instanceof \ReflectionUnionType:
|
||||
$types = $type->getTypes();
|
||||
break;
|
||||
default:
|
||||
throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
|
||||
}
|
||||
|
||||
// If there is no type restriction, it matches
|
||||
if (empty($types)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($types as $type) {
|
||||
|
||||
if ($type instanceof \ReflectionIntersectionType) {
|
||||
foreach ($type->getTypes() as $typeToMatch) {
|
||||
assert($typeToMatch instanceof \ReflectionNamedType);
|
||||
$name = $typeToMatch->getName();
|
||||
if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(isset($matches));
|
||||
} else {
|
||||
assert($type instanceof \ReflectionNamedType);
|
||||
$name = $type->getName();
|
||||
$matches = !$type->isBuiltin() && $reason instanceof $name;
|
||||
}
|
||||
|
||||
// If we look for a single match (union), we can return early on match
|
||||
// If we look for a full match (intersection), we can return early on mismatch
|
||||
if ($matches) {
|
||||
if ($isTypeUnion) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (!$isTypeUnion) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we look for a single match (union) and did not return early, we matched no type and are false
|
||||
// If we look for a full match (intersection) and did not return early, we matched all types and are true
|
||||
return $isTypeUnion ? false : true;
|
||||
}
|
||||
Reference in New Issue
Block a user