1
0
mirror of https://github.com/danog/amp.git synced 2024-12-04 10:28:01 +01:00
amp/lib/AsyncGenerator.php

145 lines
3.9 KiB
PHP
Raw Normal View History

2020-05-13 17:15:21 +02:00
<?php
namespace Amp;
/**
* @template TValue
* @template TSend
* @template TReturn
*/
2020-08-23 16:18:28 +02:00
final class AsyncGenerator implements Pipeline
2020-05-13 17:15:21 +02:00
{
2020-05-28 19:59:55 +02:00
/** @var Internal\EmitSource<TValue, TSend> */
private $source;
2020-05-13 17:15:21 +02:00
2020-07-17 18:19:36 +02:00
/** @var Coroutine<TReturn>|null */
2020-05-13 17:15:21 +02:00
private $coroutine;
/** @var \Generator|null */
private $generator;
2020-05-13 17:15:21 +02:00
/**
* @param callable(callable(TValue):Promise<TSend>):\Generator $callable
*
* @throws \Error Thrown if the callable throws any exception.
* @throws \TypeError Thrown if the callable does not return a Generator.
2020-05-13 17:15:21 +02:00
*/
public function __construct(callable $callable)
{
2020-05-28 19:59:55 +02:00
$this->source = $source = new Internal\EmitSource;
2020-05-13 17:15:21 +02:00
if (\PHP_VERSION_ID < 70100) {
2020-05-28 19:59:55 +02:00
$emit = static function ($value) use ($source): Promise {
return $source->emit($value);
2020-05-13 17:15:21 +02:00
};
} else {
2020-05-28 19:59:55 +02:00
$emit = \Closure::fromCallable([$source, "emit"]);
2020-05-13 17:15:21 +02:00
}
try {
$this->generator = $callable($emit);
} catch (\Throwable $exception) {
throw new \Error("The callable threw an exception", 0, $exception);
}
2020-05-13 17:15:21 +02:00
if (!$this->generator instanceof \Generator) {
2020-05-13 17:15:21 +02:00
throw new \TypeError("The callable did not return a Generator");
}
}
2020-05-13 17:15:21 +02:00
public function __destruct()
{
$this->source->destroy();
2020-05-13 23:48:38 +02:00
}
2020-05-13 17:15:21 +02:00
/**
* @inheritDoc
2020-05-13 17:15:21 +02:00
*/
public function continue(): Promise
{
if ($this->coroutine === null) {
$this->getReturn(); // Starts execution of the coroutine.
}
return $this->source->continue();
2020-05-13 17:15:21 +02:00
}
/**
* Sends a value to the async generator, resolving the back-pressure promise with the given value.
2020-05-28 19:59:55 +02:00
* The first emitted value must be retrieved using {@see continue()}.
2020-05-13 17:15:21 +02:00
*
* @param mixed $value Value to send to the async generator.
*
* @psalm-param TSend $value
*
2020-08-23 16:18:28 +02:00
* @return Promise<mixed|null> Resolves with null if the pipeline has completed.
2020-05-18 20:49:56 +02:00
*
2020-05-21 17:11:22 +02:00
* @psalm-return Promise<TValue|null>
*
2020-05-28 19:59:55 +02:00
* @throws \Error If the first emitted value has not been retrieved using {@see continue()}.
2020-05-13 17:15:21 +02:00
*/
public function send($value): Promise
{
return $this->source->send($value);
2020-05-13 17:15:21 +02:00
}
/**
* Throws an exception into the async generator, failing the back-pressure promise with the given exception.
2020-05-28 19:59:55 +02:00
* The first emitted value must be retrieved using {@see continue()}.
2020-05-13 17:15:21 +02:00
*
* @param \Throwable $exception Exception to throw into the async generator.
*
2020-08-23 16:18:28 +02:00
* @return Promise<mixed|null> Resolves with null if the pipeline has completed.
*
2020-05-21 17:11:22 +02:00
* @psalm-return Promise<TValue|null>
2020-05-18 20:49:56 +02:00
*
2020-05-28 19:59:55 +02:00
* @throws \Error If the first emitted value has not been retrieved using {@see continue()}.
2020-05-13 17:15:21 +02:00
*/
public function throw(\Throwable $exception): Promise
{
return $this->source->throw($exception);
2020-05-13 17:15:21 +02:00
}
/**
* Notifies the generator that the consumer is no longer interested in the generator output.
*
* @return void
*/
public function dispose()
{
$this->source->dispose();
}
2020-05-13 17:15:21 +02:00
/**
* @return Promise<mixed>
*
* @psalm-return Promise<TReturn>
2020-05-13 17:15:21 +02:00
*/
public function getReturn(): Promise
{
if ($this->coroutine !== null) {
return $this->coroutine;
}
2020-07-17 18:19:36 +02:00
/** @psalm-suppress PossiblyNullArgument */
$this->coroutine = new Coroutine($this->generator);
$this->generator = null;
$source = $this->source;
$this->coroutine->onResolve(static function ($exception) use ($source) {
if ($source->isDisposed()) {
2020-07-17 18:19:36 +02:00
return; // AsyncGenerator object was destroyed.
}
if ($exception) {
$source->fail($exception);
return;
}
$source->complete();
});
2020-05-13 17:15:21 +02:00
return $this->coroutine;
}
}