123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- <?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;
- }
|