first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,55 @@
<?php
namespace React\Promise;
class CancellationQueue
{
private $started = false;
private $queue = [];
public function __invoke()
{
if ($this->started) {
return;
}
$this->started = true;
$this->drain();
}
public function enqueue($cancellable)
{
if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
return;
}
$length = \array_push($this->queue, $cancellable);
if ($this->started && 1 === $length) {
$this->drain();
}
}
private function drain()
{
for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
$cancellable = $this->queue[$i];
$exception = null;
try {
$cancellable->cancel();
} catch (\Throwable $exception) {
} catch (\Exception $exception) {
}
unset($this->queue[$i]);
if ($exception) {
throw $exception;
}
}
$this->queue = [];
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace React\Promise;
class Deferred implements PromisorInterface
{
private $promise;
private $resolveCallback;
private $rejectCallback;
private $notifyCallback;
private $canceller;
public function __construct(callable $canceller = null)
{
$this->canceller = $canceller;
}
public function promise()
{
if (null === $this->promise) {
$this->promise = new Promise(function ($resolve, $reject, $notify) {
$this->resolveCallback = $resolve;
$this->rejectCallback = $reject;
$this->notifyCallback = $notify;
}, $this->canceller);
$this->canceller = null;
}
return $this->promise;
}
public function resolve($value = null)
{
$this->promise();
\call_user_func($this->resolveCallback, $value);
}
public function reject($reason = null)
{
$this->promise();
\call_user_func($this->rejectCallback, $reason);
}
/**
* @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
* @param mixed $update
*/
public function notify($update = null)
{
$this->promise();
\call_user_func($this->notifyCallback, $update);
}
/**
* @deprecated 2.2.0
* @see Deferred::notify()
*/
public function progress($update = null)
{
$this->notify($update);
}
}

View File

@@ -0,0 +1,7 @@
<?php
namespace React\Promise\Exception;
class LengthException extends \LengthException
{
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,256 @@
<?php
namespace React\Promise;
class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
private $canceller;
private $result;
private $handlers = [];
private $progressHandlers = [];
private $requiredCancelRequests = 0;
private $cancelRequests = 0;
public function __construct(callable $resolver, callable $canceller = null)
{
$this->canceller = $canceller;
// Explicitly overwrite arguments with null values before invoking
// resolver function. This ensure that these arguments do not show up
// in the stack trace in PHP 7+ only.
$cb = $resolver;
$resolver = $canceller = null;
$this->call($cb);
}
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->then($onFulfilled, $onRejected, $onProgress);
}
if (null === $this->canceller) {
return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
}
// This promise has a canceller, so we create a new child promise which
// has a canceller that invokes the parent canceller if all other
// followers are also cancelled. We keep a reference to this promise
// instance for the static canceller function and clear this to avoid
// keeping a cyclic reference between parent and follower.
$parent = $this;
++$parent->requiredCancelRequests;
return new static(
$this->resolver($onFulfilled, $onRejected, $onProgress),
static function () use (&$parent) {
if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
$parent->cancel();
}
$parent = null;
}
);
}
public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
if (null !== $this->result) {
return $this->result->done($onFulfilled, $onRejected, $onProgress);
}
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
$promise
->done($onFulfilled, $onRejected);
};
if ($onProgress) {
$this->progressHandlers[] = $onProgress;
}
}
public function otherwise(callable $onRejected)
{
return $this->then(null, static function ($reason) use ($onRejected) {
if (!_checkTypehint($onRejected, $reason)) {
return new RejectedPromise($reason);
}
return $onRejected($reason);
});
}
public function always(callable $onFulfilledOrRejected)
{
return $this->then(static function ($value) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($value) {
return $value;
});
}, static function ($reason) use ($onFulfilledOrRejected) {
return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
return new RejectedPromise($reason);
});
});
}
public function progress(callable $onProgress)
{
return $this->then(null, null, $onProgress);
}
public function cancel()
{
if (null === $this->canceller || null !== $this->result) {
return;
}
$canceller = $this->canceller;
$this->canceller = null;
$this->call($canceller);
}
private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
{
return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
if ($onProgress) {
$progressHandler = static function ($update) use ($notify, $onProgress) {
try {
$notify($onProgress($update));
} catch (\Throwable $e) {
$notify($e);
} catch (\Exception $e) {
$notify($e);
}
};
} else {
$progressHandler = $notify;
}
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
$promise
->then($onFulfilled, $onRejected)
->done($resolve, $reject, $progressHandler);
};
$this->progressHandlers[] = $progressHandler;
};
}
private function reject($reason = null)
{
if (null !== $this->result) {
return;
}
$this->settle(reject($reason));
}
private function settle(ExtendedPromiseInterface $promise)
{
$promise = $this->unwrap($promise);
if ($promise === $this) {
$promise = new RejectedPromise(
new \LogicException('Cannot resolve a promise with itself.')
);
}
$handlers = $this->handlers;
$this->progressHandlers = $this->handlers = [];
$this->result = $promise;
$this->canceller = null;
foreach ($handlers as $handler) {
$handler($promise);
}
}
private function unwrap($promise)
{
$promise = $this->extract($promise);
while ($promise instanceof self && null !== $promise->result) {
$promise = $this->extract($promise->result);
}
return $promise;
}
private function extract($promise)
{
if ($promise instanceof LazyPromise) {
$promise = $promise->promise();
}
return $promise;
}
private function call(callable $cb)
{
// Explicitly overwrite argument with null value. This ensure that this
// argument does not show up in the stack trace in PHP 7+ only.
$callback = $cb;
$cb = null;
// Use reflection to inspect number of arguments expected by this callback.
// We did some careful benchmarking here: Using reflection to avoid unneeded
// function arguments is actually faster than blindly passing them.
// Also, this helps avoiding unnecessary function arguments in the call stack
// if the callback creates an Exception (creating garbage cycles).
if (\is_array($callback)) {
$ref = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$ref = new \ReflectionMethod($callback, '__invoke');
} else {
$ref = new \ReflectionFunction($callback);
}
$args = $ref->getNumberOfParameters();
try {
if ($args === 0) {
$callback();
} else {
// Keep references to this promise instance for the static resolve/reject functions.
// By using static callbacks that are not bound to this instance
// and passing the target promise instance by reference, we can
// still execute its resolving logic and still clear this
// reference when settling the promise. This helps avoiding
// garbage cycles if any callback creates an Exception.
// These assumptions are covered by the test suite, so if you ever feel like
// refactoring this, go ahead, any alternative suggestions are welcome!
$target =& $this;
$progressHandlers =& $this->progressHandlers;
$callback(
static function ($value = null) use (&$target) {
if ($target !== null) {
$target->settle(resolve($value));
$target = null;
}
},
static function ($reason = null) use (&$target) {
if ($target !== null) {
$target->reject($reason);
$target = null;
}
},
static function ($update = null) use (&$progressHandlers) {
foreach ($progressHandlers as $handler) {
$handler($update);
}
}
);
}
} catch (\Throwable $e) {
$target = null;
$this->reject($e);
} catch (\Exception $e) {
$target = null;
$this->reject($e);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace React\Promise;
interface PromiseInterface
{
/**
* Transforms a promise's value by applying a function to the promise's fulfillment
* or rejection value. Returns a new promise for the transformed result.
*
* The `then()` method registers new fulfilled and rejection handlers with a promise
* (all parameters are optional):
*
* * `$onFulfilled` will be invoked once the promise is fulfilled and passed
* the result as the first argument.
* * `$onRejected` will be invoked once the promise is rejected and passed the
* reason as the first argument.
* * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
* triggers progress notifications and passed a single argument (whatever it
* wants) to indicate progress.
*
* It returns a new promise that will fulfill with the return value of either
* `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
* the thrown exception if either throws.
*
* A promise makes the following guarantees about handlers registered in
* the same call to `then()`:
*
* 1. Only one of `$onFulfilled` or `$onRejected` will be called,
* never both.
* 2. `$onFulfilled` and `$onRejected` will never be called more
* than once.
* 3. `$onProgress` (deprecated) may be called multiple times.
*
* @param callable|null $onFulfilled
* @param callable|null $onRejected
* @param callable|null $onProgress This argument is deprecated and should not be used anymore.
* @return PromiseInterface
*/
public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,351 @@
<?php
namespace React\Promise;
/**
* 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.
*
* @param mixed $promiseOrValue
* @return PromiseInterface
*/
function resolve($promiseOrValue = null)
{
if ($promiseOrValue instanceof ExtendedPromiseInterface) {
return $promiseOrValue;
}
// Check is_object() first to avoid method_exists() triggering
// class autoloaders if $promiseOrValue is a string.
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
$canceller = null;
if (\method_exists($promiseOrValue, 'cancel')) {
$canceller = [$promiseOrValue, 'cancel'];
}
return new Promise(function ($resolve, $reject, $notify) use ($promiseOrValue) {
$promiseOrValue->then($resolve, $reject, $notify);
}, $canceller);
}
return new FulfilledPromise($promiseOrValue);
}
/**
* Creates a rejected promise for the supplied `$promiseOrValue`.
*
* If `$promiseOrValue` is a value, it will be the rejection value of the
* returned promise.
*
* If `$promiseOrValue` is a promise, its completion value will be the rejected
* value of the returned promise.
*
* This can be useful in situations where you need to reject a promise without
* throwing an exception. For example, it allows you to propagate a rejection with
* the value of another promise.
*
* @param mixed $promiseOrValue
* @return PromiseInterface
*/
function reject($promiseOrValue = null)
{
if ($promiseOrValue instanceof PromiseInterface) {
return resolve($promiseOrValue)->then(function ($value) {
return new RejectedPromise($value);
});
}
return new RejectedPromise($promiseOrValue);
}
/**
* Returns a promise that will resolve only once all the items in
* `$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`.
*
* @param array $promisesOrValues
* @return PromiseInterface
*/
function all($promisesOrValues)
{
return map($promisesOrValues, function ($val) {
return $val;
});
}
/**
* 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.
*
* @param array $promisesOrValues
* @return PromiseInterface
*/
function race($promisesOrValues)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
if (!is_array($array) || !$array) {
$resolve();
return;
}
foreach ($array as $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)
->done($resolve, $reject, $notify);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* 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.
*
* @param array $promisesOrValues
* @return PromiseInterface
*/
function any($promisesOrValues)
{
return some($promisesOrValues, 1)
->then(function ($val) {
return \array_shift($val);
});
}
/**
* Returns a promise that will resolve when `$howMany` of the supplied items in
* `$promisesOrValues` resolve. The resolution value of the returned promise
* will be an array of length `$howMany` containing the resolution values of the
* triggering items.
*
* The returned promise will reject if it becomes impossible for `$howMany` items
* to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
* reject). The rejection value will be an array of
* `(count($promisesOrValues) - $howMany) + 1` rejection reasons.
*
* The returned promise will also reject with a `React\Promise\Exception\LengthException`
* if `$promisesOrValues` contains less items than `$howMany`.
*
* @param array $promisesOrValues
* @param int $howMany
* @return PromiseInterface
*/
function some($promisesOrValues, $howMany)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array) || $howMany < 1) {
$resolve([]);
return;
}
$len = \count($array);
if ($len < $howMany) {
throw new Exception\LengthException(
\sprintf(
'Input array must contain at least %d item%s but contains only %s item%s.',
$howMany,
1 === $howMany ? '' : 's',
$len,
1 === $len ? '' : 's'
)
);
}
$toResolve = $howMany;
$toReject = ($len - $toResolve) + 1;
$values = [];
$reasons = [];
foreach ($array as $i => $promiseOrValue) {
$fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
$values[$i] = $val;
if (0 === --$toResolve) {
$resolve($values);
}
};
$rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
if ($toResolve < 1 || $toReject < 1) {
return;
}
$reasons[$i] = $reason;
if (0 === --$toReject) {
$reject($reasons);
}
};
$cancellationQueue->enqueue($promiseOrValue);
resolve($promiseOrValue)
->done($fulfiller, $rejecter, $notify);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* Traditional map function, similar to `array_map()`, but allows input to contain
* promises and/or values, and `$mapFunc` may return either a value or a promise.
*
* The map function receives each item as argument, where item is a fully resolved
* value of a promise or value in `$promisesOrValues`.
*
* @param array $promisesOrValues
* @param callable $mapFunc
* @return PromiseInterface
*/
function map($promisesOrValues, callable $mapFunc)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array) || !$array) {
$resolve([]);
return;
}
$toResolve = \count($array);
$values = [];
foreach ($array as $i => $promiseOrValue) {
$cancellationQueue->enqueue($promiseOrValue);
$values[$i] = null;
resolve($promiseOrValue)
->then($mapFunc)
->done(
function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
$values[$i] = $mapped;
if (0 === --$toResolve) {
$resolve($values);
}
},
$reject,
$notify
);
}
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* Traditional reduce function, similar to `array_reduce()`, but input may contain
* promises and/or values, and `$reduceFunc` may return either a value or a
* promise, *and* `$initialValue` may be a promise or a value for the starting
* value.
*
* @param array $promisesOrValues
* @param callable $reduceFunc
* @param mixed $initialValue
* @return PromiseInterface
*/
function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
{
$cancellationQueue = new CancellationQueue();
$cancellationQueue->enqueue($promisesOrValues);
return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
resolve($promisesOrValues)
->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
if (!\is_array($array)) {
$array = [];
}
$total = \count($array);
$i = 0;
// Wrap the supplied $reduceFunc with one that handles promises and then
// delegates to the supplied.
$wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
$cancellationQueue->enqueue($val);
return $current
->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
return resolve($val)
->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
return $reduceFunc($c, $value, $i++, $total);
});
});
};
$cancellationQueue->enqueue($initialValue);
\array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
->done($resolve, $reject, $notify);
}, $reject, $notify);
}, $cancellationQueue);
}
/**
* @internal
*/
function _checkTypehint(callable $callback, $object)
{
if (!\is_object($object)) {
return true;
}
if (\is_array($callback)) {
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
} elseif (\is_object($callback) && !$callback instanceof \Closure) {
$callbackReflection = new \ReflectionMethod($callback, '__invoke');
} else {
$callbackReflection = new \ReflectionFunction($callback);
}
$parameters = $callbackReflection->getParameters();
if (!isset($parameters[0])) {
return true;
}
$expectedException = $parameters[0];
if (!$expectedException->getClass()) {
return true;
}
return $expectedException->getClass()->isInstance($object);
}

View File

@@ -0,0 +1,5 @@
<?php
if (!\function_exists('React\Promise\resolve')) {
require __DIR__.'/functions.php';
}