1
0
mirror of https://github.com/danog/amp.git synced 2025-01-23 05:41:25 +01:00
amp/lib/AsyncGenerator.php

151 lines
4.1 KiB
PHP
Raw Normal View History

2020-05-13 10:15:21 -05:00
<?php
namespace Amp;
use Amp\Future;
use function Amp\Future\spawn;
2021-04-04 13:10:23 -05:00
2020-05-13 10:15:21 -05:00
/**
* @template TValue
* @template TSend
* @template TReturn
*
* @template-implements Pipeline<TValue>
* @template-implements \IteratorAggregate<int, TValue>
2020-05-13 10:15:21 -05:00
*/
final class AsyncGenerator implements Pipeline, \IteratorAggregate
2020-05-13 10:15:21 -05:00
{
2020-05-28 12:59:55 -05:00
/** @var Internal\EmitSource<TValue, TSend> */
2020-09-24 11:52:22 -05:00
private Internal\EmitSource $source;
2020-05-13 10:15:21 -05:00
2021-04-04 13:10:23 -05:00
/** @var Future<TReturn> */
private Future $future;
2020-05-13 10:15:21 -05:00
/**
2020-09-24 22:14:58 -05:00
* @param callable(mixed ...$args):\Generator $callable
* @param mixed ...$args Arguments passed to callback.
2020-05-13 10:15:21 -05:00
*
* @throws \Error Thrown if the callable throws any exception.
* @throws \TypeError Thrown if the callable does not return a Generator.
2020-05-13 10:15:21 -05:00
*/
2020-09-24 22:14:58 -05:00
public function __construct(callable $callable, mixed ...$args)
2020-05-13 10:15:21 -05:00
{
2020-05-28 12:59:55 -05:00
$this->source = $source = new Internal\EmitSource;
2020-05-13 10:15:21 -05:00
try {
2020-09-24 22:14:58 -05:00
$generator = $callable(...$args);
2020-05-13 10:15:21 -05:00
if (!$generator instanceof \Generator) {
throw new \TypeError("The callable did not return a Generator");
}
} catch (\Throwable $exception) {
$this->source->error($exception);
$this->future = Future::error($exception);
return;
2020-05-13 10:15:21 -05:00
}
2020-08-28 11:51:43 -05:00
$this->future = spawn(static function () use ($generator, $source): mixed {
try {
$yielded = $generator->current();
2020-09-24 22:14:58 -05:00
while ($generator->valid()) {
try {
$yielded = $generator->send($source->yield($yielded));
} catch (DisposedException $exception) {
throw $exception; // Destroys generator and fails pipeline.
} catch (\Throwable $exception) {
$yielded = $generator->throw($exception);
}
2020-09-24 22:14:58 -05:00
}
2021-04-04 13:10:23 -05:00
} catch (\Throwable $exception) {
$source->error($exception);
throw $exception;
2020-08-28 11:51:43 -05:00
}
$source->complete();
return $generator->getReturn();
2020-08-28 11:51:43 -05:00
});
}
2020-05-13 10:15:21 -05:00
public function __destruct()
{
$this->source->destroy();
2020-05-13 16:48:38 -05:00
}
2020-05-13 10:15:21 -05:00
/**
* @inheritDoc
*
* @psalm-return TValue|null
2020-05-13 10:15:21 -05:00
*/
2020-09-24 22:14:58 -05:00
public function continue(): mixed
2020-05-13 10:15:21 -05:00
{
return $this->source->continue();
2020-05-13 10:15:21 -05:00
}
/**
* Sends a value to the async generator, resolving the back-pressure promise with the given value.
2020-05-28 12:59:55 -05:00
* The first emitted value must be retrieved using {@see continue()}.
2020-05-13 10:15:21 -05:00
*
* @param mixed $value Value to send to the async generator.
*
* @psalm-param TSend $value
*
2021-04-04 13:10:23 -05:00
* @return mixed Returns null if the pipeline has completed.
2020-05-18 13:49:56 -05:00
*
2021-04-04 13:10:23 -05:00
* @psalm-return TValue
*
2020-05-28 12:59:55 -05:00
* @throws \Error If the first emitted value has not been retrieved using {@see continue()}.
2020-05-13 10:15:21 -05:00
*/
2020-09-25 12:57:56 -05:00
public function send(mixed $value): mixed
2020-05-13 10:15:21 -05:00
{
return $this->source->send($value);
2020-05-13 10:15:21 -05:00
}
/**
* Throws an exception into the async generator, failing the back-pressure promise with the given exception.
2020-05-28 12:59:55 -05:00
* The first emitted value must be retrieved using {@see continue()}.
2020-05-13 10:15:21 -05:00
*
* @param \Throwable $exception Exception to throw into the async generator.
*
2021-04-04 13:10:23 -05:00
* @return mixed Returns null if the pipeline has completed.
*
2021-04-04 13:10:23 -05:00
* @psalm-return TValue
2020-05-18 13:49:56 -05:00
*
2020-05-28 12:59:55 -05:00
* @throws \Error If the first emitted value has not been retrieved using {@see continue()}.
2020-05-13 10:15:21 -05:00
*/
2020-09-24 22:14:58 -05:00
public function throw(\Throwable $exception): mixed
2020-05-13 10:15:21 -05:00
{
return $this->source->throw($exception);
2020-05-13 10:15:21 -05:00
}
/**
* Notifies the generator that the consumer is no longer interested in the generator output.
*
* @return void
*/
2020-09-24 22:14:58 -05:00
public function dispose(): void
{
$this->source->dispose();
}
2020-05-13 10:15:21 -05:00
/**
2020-09-24 22:14:58 -05:00
* @psalm-return TReturn
2020-05-13 10:15:21 -05:00
*/
2020-09-24 22:14:58 -05:00
public function getReturn(): mixed
2020-05-13 10:15:21 -05:00
{
2021-04-04 13:10:23 -05:00
return $this->future->join();
2020-05-13 10:15:21 -05:00
}
/**
* @inheritDoc
*
* @paslm-return \Traversable<int, TValue>
*/
public function getIterator(): \Traversable
{
while (null !== $value = $this->source->continue()) {
yield $value;
}
}
2020-05-13 10:15:21 -05:00
}