| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- <?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);
- }
- }
- }
|