/** * {@inheritdoc} */ public function when(callable $onResolved) { try { $onResolved(null, $this->value); } catch (\Throwable $exception) { Loop::defer(static function () use($exception) { throw $exception; }); } }
/** * @param \Generator $generator */ public function __construct(\Generator $generator) { $this->generator = $generator; /** * @param \Throwable|null $exception Exception to be thrown into the generator. * @param mixed $value Value to be sent into the generator. */ $this->when = function ($exception, $value) { if ($this->depth > self::MAX_CONTINUATION_DEPTH) { // Defer continuation to avoid blowing up call stack. Loop::defer(function () use($exception, $value) { ($this->when)($exception, $value); }); return; } try { if ($exception) { // Throw exception at current execution point. $yielded = $this->generator->throw($exception); } else { // Send the new value and execute to next yield statement. $yielded = $this->generator->send($value); } if ($yielded instanceof Promise) { ++$this->depth; $yielded->when($this->when); --$this->depth; return; } if ($this->generator->valid()) { $got = \is_object($yielded) ? \get_class($yielded) : \gettype($yielded); throw new InvalidYieldError($this->generator, \sprintf("Unexpected yield (%s expected, got %s)", Promise::class, $got)); } $this->resolve($this->generator->getReturn()); } catch (\Throwable $exception) { $this->dispose($exception); } }; try { $yielded = $this->generator->current(); if ($yielded instanceof Promise) { ++$this->depth; $yielded->when($this->when); --$this->depth; return; } if ($this->generator->valid()) { $got = \is_object($yielded) ? \get_class($yielded) : \gettype($yielded); throw new InvalidYieldError($this->generator, \sprintf("Unexpected yield (%s expected, got %s)", Promise::class, $got)); } $this->resolve($this->generator->getReturn()); } catch (\Throwable $exception) { $this->dispose($exception); } }
/** * Emits a value from the observable. The returned promise is resolved with the emitted value once all subscribers * have been invoked. * * @param mixed $value * * @return \Interop\Async\Promise * * @throws \Error If the observable has resolved. */ private function emit($value) : Promise { if ($this->resolved) { throw new \Error("The observable has been resolved; cannot emit more values"); } if ($value instanceof Promise) { $deferred = new Deferred(); $value->when(function ($e, $v) use($deferred) { if ($this->resolved) { $deferred->fail(new \Error("The observable was resolved before the promise result could be emitted")); return; } if ($e) { $this->fail($e); $deferred->fail($e); return; } $deferred->resolve($this->emit($v)); }); return $deferred->promise(); } $promises = []; foreach ($this->subscribers as $onNext) { try { $result = $onNext($value); if ($result instanceof Promise) { $promises[] = $result; } } catch (\Throwable $e) { Loop::defer(static function () use($e) { throw $e; }); } } if (!$promises) { return new Success($value); } $deferred = new Deferred(); $count = \count($promises); $f = static function ($e) use($deferred, $value, &$count) { if ($e) { Loop::defer(static function () use($e) { throw $e; }); } if (!--$count) { $deferred->resolve($value); } }; foreach ($promises as $promise) { $promise->when($f); } return $deferred->promise(); }
/** * @param mixed $value * * @throws \Error Thrown if the promise has already been resolved. */ private function resolve($value = null) { if ($this->resolved) { throw new \Error("Promise has already been resolved"); } $this->resolved = true; $this->result = $value; if ($this->onResolved === null) { return; } $onResolved = $this->onResolved; $this->onResolved = null; if ($this->result instanceof Promise) { $this->result->when($onResolved); return; } try { $onResolved(null, $this->result); } catch (\Throwable $exception) { Loop::defer(static function () use($exception) { throw $exception; }); } }
/** * Defer the execution of a callback. * Returned Generators are run as coroutines. Failures of the coroutine are forwarded to the loop error handler. * * @see \Interop\Async\Loop::defer() * * @param callable(string $watcherId, mixed $data) $callback The callback to delay. * @param mixed $data * * @return string Watcher identifier. */ function defer(callable $callback, $data = null) : string { return Loop::defer(function ($watcherId, $data) use($callback) { $result = $callback($watcherId, $data); if ($result instanceof \Generator) { rethrow(new Coroutine($result)); } }, $data); }