vendor/guzzlehttp/promises/src/Promise.php line 209

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace GuzzleHttp\Promise;
  4. /**
  5.  * Promises/A+ implementation that avoids recursion when possible.
  6.  *
  7.  * @see https://promisesaplus.com/
  8.  *
  9.  * @final
  10.  */
  11. class Promise implements PromiseInterface
  12. {
  13.     private $state self::PENDING;
  14.     private $result;
  15.     private $cancelFn;
  16.     private $waitFn;
  17.     private $waitList;
  18.     private $handlers = [];
  19.     /**
  20.      * @param callable $waitFn   Fn that when invoked resolves the promise.
  21.      * @param callable $cancelFn Fn that when invoked cancels the promise.
  22.      */
  23.     public function __construct(
  24.         callable $waitFn null,
  25.         callable $cancelFn null
  26.     ) {
  27.         $this->waitFn $waitFn;
  28.         $this->cancelFn $cancelFn;
  29.     }
  30.     public function then(
  31.         callable $onFulfilled null,
  32.         callable $onRejected null
  33.     ): PromiseInterface {
  34.         if ($this->state === self::PENDING) {
  35.             $p = new Promise(null, [$this'cancel']);
  36.             $this->handlers[] = [$p$onFulfilled$onRejected];
  37.             $p->waitList $this->waitList;
  38.             $p->waitList[] = $this;
  39.             return $p;
  40.         }
  41.         // Return a fulfilled promise and immediately invoke any callbacks.
  42.         if ($this->state === self::FULFILLED) {
  43.             $promise Create::promiseFor($this->result);
  44.             return $onFulfilled $promise->then($onFulfilled) : $promise;
  45.         }
  46.         // It's either cancelled or rejected, so return a rejected promise
  47.         // and immediately invoke any callbacks.
  48.         $rejection Create::rejectionFor($this->result);
  49.         return $onRejected $rejection->then(null$onRejected) : $rejection;
  50.     }
  51.     public function otherwise(callable $onRejected): PromiseInterface
  52.     {
  53.         return $this->then(null$onRejected);
  54.     }
  55.     public function wait(bool $unwrap true)
  56.     {
  57.         $this->waitIfPending();
  58.         if ($this->result instanceof PromiseInterface) {
  59.             return $this->result->wait($unwrap);
  60.         }
  61.         if ($unwrap) {
  62.             if ($this->state === self::FULFILLED) {
  63.                 return $this->result;
  64.             }
  65.             // It's rejected so "unwrap" and throw an exception.
  66.             throw Create::exceptionFor($this->result);
  67.         }
  68.     }
  69.     public function getState(): string
  70.     {
  71.         return $this->state;
  72.     }
  73.     public function cancel(): void
  74.     {
  75.         if ($this->state !== self::PENDING) {
  76.             return;
  77.         }
  78.         $this->waitFn $this->waitList null;
  79.         if ($this->cancelFn) {
  80.             $fn $this->cancelFn;
  81.             $this->cancelFn null;
  82.             try {
  83.                 $fn();
  84.             } catch (\Throwable $e) {
  85.                 $this->reject($e);
  86.             }
  87.         }
  88.         // Reject the promise only if it wasn't rejected in a then callback.
  89.         /** @psalm-suppress RedundantCondition */
  90.         if ($this->state === self::PENDING) {
  91.             $this->reject(new CancellationException('Promise has been cancelled'));
  92.         }
  93.     }
  94.     public function resolve($value): void
  95.     {
  96.         $this->settle(self::FULFILLED$value);
  97.     }
  98.     public function reject($reason): void
  99.     {
  100.         $this->settle(self::REJECTED$reason);
  101.     }
  102.     private function settle(string $state$value): void
  103.     {
  104.         if ($this->state !== self::PENDING) {
  105.             // Ignore calls with the same resolution.
  106.             if ($state === $this->state && $value === $this->result) {
  107.                 return;
  108.             }
  109.             throw $this->state === $state
  110.                 ? new \LogicException("The promise is already {$state}.")
  111.                 : new \LogicException("Cannot change a {$this->state} promise to {$state}");
  112.         }
  113.         if ($value === $this) {
  114.             throw new \LogicException('Cannot fulfill or reject a promise with itself');
  115.         }
  116.         // Clear out the state of the promise but stash the handlers.
  117.         $this->state $state;
  118.         $this->result $value;
  119.         $handlers $this->handlers;
  120.         $this->handlers null;
  121.         $this->waitList $this->waitFn null;
  122.         $this->cancelFn null;
  123.         if (!$handlers) {
  124.             return;
  125.         }
  126.         // If the value was not a settled promise or a thenable, then resolve
  127.         // it in the task queue using the correct ID.
  128.         if (!is_object($value) || !method_exists($value'then')) {
  129.             $id $state === self::FULFILLED 2;
  130.             // It's a success, so resolve the handlers in the queue.
  131.             Utils::queue()->add(static function () use ($id$value$handlers): void {
  132.                 foreach ($handlers as $handler) {
  133.                     self::callHandler($id$value$handler);
  134.                 }
  135.             });
  136.         } elseif ($value instanceof Promise && Is::pending($value)) {
  137.             // We can just merge our handlers onto the next promise.
  138.             $value->handlers array_merge($value->handlers$handlers);
  139.         } else {
  140.             // Resolve the handlers when the forwarded promise is resolved.
  141.             $value->then(
  142.                 static function ($value) use ($handlers): void {
  143.                     foreach ($handlers as $handler) {
  144.                         self::callHandler(1$value$handler);
  145.                     }
  146.                 },
  147.                 static function ($reason) use ($handlers): void {
  148.                     foreach ($handlers as $handler) {
  149.                         self::callHandler(2$reason$handler);
  150.                     }
  151.                 }
  152.             );
  153.         }
  154.     }
  155.     /**
  156.      * Call a stack of handlers using a specific callback index and value.
  157.      *
  158.      * @param int   $index   1 (resolve) or 2 (reject).
  159.      * @param mixed $value   Value to pass to the callback.
  160.      * @param array $handler Array of handler data (promise and callbacks).
  161.      */
  162.     private static function callHandler(int $index$value, array $handler): void
  163.     {
  164.         /** @var PromiseInterface $promise */
  165.         $promise $handler[0];
  166.         // The promise may have been cancelled or resolved before placing
  167.         // this thunk in the queue.
  168.         if (Is::settled($promise)) {
  169.             return;
  170.         }
  171.         try {
  172.             if (isset($handler[$index])) {
  173.                 /*
  174.                  * If $f throws an exception, then $handler will be in the exception
  175.                  * stack trace. Since $handler contains a reference to the callable
  176.                  * itself we get a circular reference. We clear the $handler
  177.                  * here to avoid that memory leak.
  178.                  */
  179.                 $f $handler[$index];
  180.                 unset($handler);
  181.                 $promise->resolve($f($value));
  182.             } elseif ($index === 1) {
  183.                 // Forward resolution values as-is.
  184.                 $promise->resolve($value);
  185.             } else {
  186.                 // Forward rejections down the chain.
  187.                 $promise->reject($value);
  188.             }
  189.         } catch (\Throwable $reason) {
  190.             $promise->reject($reason);
  191.         }
  192.     }
  193.     private function waitIfPending(): void
  194.     {
  195.         if ($this->state !== self::PENDING) {
  196.             return;
  197.         } elseif ($this->waitFn) {
  198.             $this->invokeWaitFn();
  199.         } elseif ($this->waitList) {
  200.             $this->invokeWaitList();
  201.         } else {
  202.             // If there's no wait function, then reject the promise.
  203.             $this->reject('Cannot wait on a promise that has '
  204.                 .'no internal wait function. You must provide a wait '
  205.                 .'function when constructing the promise to be able to '
  206.                 .'wait on a promise.');
  207.         }
  208.         Utils::queue()->run();
  209.         /** @psalm-suppress RedundantCondition */
  210.         if ($this->state === self::PENDING) {
  211.             $this->reject('Invoking the wait callback did not resolve the promise');
  212.         }
  213.     }
  214.     private function invokeWaitFn(): void
  215.     {
  216.         try {
  217.             $wfn $this->waitFn;
  218.             $this->waitFn null;
  219.             $wfn(true);
  220.         } catch (\Throwable $reason) {
  221.             if ($this->state === self::PENDING) {
  222.                 // The promise has not been resolved yet, so reject the promise
  223.                 // with the exception.
  224.                 $this->reject($reason);
  225.             } else {
  226.                 // The promise was already resolved, so there's a problem in
  227.                 // the application.
  228.                 throw $reason;
  229.             }
  230.         }
  231.     }
  232.     private function invokeWaitList(): void
  233.     {
  234.         $waitList $this->waitList;
  235.         $this->waitList null;
  236.         foreach ($waitList as $result) {
  237.             do {
  238.                 $result->waitIfPending();
  239.                 $result $result->result;
  240.             } while ($result instanceof Promise);
  241.             if ($result instanceof PromiseInterface) {
  242.                 $result->wait(false);
  243.             }
  244.         }
  245.     }
  246. }