Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  namespace GuzzleHttp\Promise;
   4  
   5  use Exception;
   6  use Generator;
   7  use Throwable;
   8  
   9  /**
  10   * Creates a promise that is resolved using a generator that yields values or
  11   * promises (somewhat similar to C#'s async keyword).
  12   *
  13   * When called, the Coroutine::of method will start an instance of the generator
  14   * and returns a promise that is fulfilled with its final yielded value.
  15   *
  16   * Control is returned back to the generator when the yielded promise settles.
  17   * This can lead to less verbose code when doing lots of sequential async calls
  18   * with minimal processing in between.
  19   *
  20   *     use GuzzleHttp\Promise;
  21   *
  22   *     function createPromise($value) {
  23   *         return new Promise\FulfilledPromise($value);
  24   *     }
  25   *
  26   *     $promise = Promise\Coroutine::of(function () {
  27   *         $value = (yield createPromise('a'));
  28   *         try {
  29   *             $value = (yield createPromise($value . 'b'));
  30   *         } catch (\Exception $e) {
  31   *             // The promise was rejected.
  32   *         }
  33   *         yield $value . 'c';
  34   *     });
  35   *
  36   *     // Outputs "abc"
  37   *     $promise->then(function ($v) { echo $v; });
  38   *
  39   * @param callable $generatorFn Generator function to wrap into a promise.
  40   *
  41   * @return Promise
  42   *
  43   * @link https://github.com/petkaantonov/bluebird/blob/master/API.md#generators inspiration
  44   */
  45  final class Coroutine implements PromiseInterface
  46  {
  47      /**
  48       * @var PromiseInterface|null
  49       */
  50      private $currentPromise;
  51  
  52      /**
  53       * @var Generator
  54       */
  55      private $generator;
  56  
  57      /**
  58       * @var Promise
  59       */
  60      private $result;
  61  
  62      public function __construct(callable $generatorFn)
  63      {
  64          $this->generator = $generatorFn();
  65          $this->result = new Promise(function () {
  66              while (isset($this->currentPromise)) {
  67                  $this->currentPromise->wait();
  68              }
  69          });
  70          try {
  71              $this->nextCoroutine($this->generator->current());
  72          } catch (\Exception $exception) {
  73              $this->result->reject($exception);
  74          } catch (Throwable $throwable) {
  75              $this->result->reject($throwable);
  76          }
  77      }
  78  
  79      /**
  80       * Create a new coroutine.
  81       *
  82       * @return self
  83       */
  84      public static function of(callable $generatorFn)
  85      {
  86          return new self($generatorFn);
  87      }
  88  
  89      public function then(
  90          callable $onFulfilled = null,
  91          callable $onRejected = null
  92      ) {
  93          return $this->result->then($onFulfilled, $onRejected);
  94      }
  95  
  96      public function otherwise(callable $onRejected)
  97      {
  98          return $this->result->otherwise($onRejected);
  99      }
 100  
 101      public function wait($unwrap = true)
 102      {
 103          return $this->result->wait($unwrap);
 104      }
 105  
 106      public function getState()
 107      {
 108          return $this->result->getState();
 109      }
 110  
 111      public function resolve($value)
 112      {
 113          $this->result->resolve($value);
 114      }
 115  
 116      public function reject($reason)
 117      {
 118          $this->result->reject($reason);
 119      }
 120  
 121      public function cancel()
 122      {
 123          $this->currentPromise->cancel();
 124          $this->result->cancel();
 125      }
 126  
 127      private function nextCoroutine($yielded)
 128      {
 129          $this->currentPromise = Create::promiseFor($yielded)
 130              ->then([$this, '_handleSuccess'], [$this, '_handleFailure']);
 131      }
 132  
 133      /**
 134       * @internal
 135       */
 136      public function _handleSuccess($value)
 137      {
 138          unset($this->currentPromise);
 139          try {
 140              $next = $this->generator->send($value);
 141              if ($this->generator->valid()) {
 142                  $this->nextCoroutine($next);
 143              } else {
 144                  $this->result->resolve($value);
 145              }
 146          } catch (Exception $exception) {
 147              $this->result->reject($exception);
 148          } catch (Throwable $throwable) {
 149              $this->result->reject($throwable);
 150          }
 151      }
 152  
 153      /**
 154       * @internal
 155       */
 156      public function _handleFailure($reason)
 157      {
 158          unset($this->currentPromise);
 159          try {
 160              $nextYield = $this->generator->throw(Create::exceptionFor($reason));
 161              // The throw was caught, so keep iterating on the coroutine
 162              $this->nextCoroutine($nextYield);
 163          } catch (Exception $exception) {
 164              $this->result->reject($exception);
 165          } catch (Throwable $throwable) {
 166              $this->result->reject($throwable);
 167          }
 168      }
 169  }