2020-05-13 10:15:21 -05:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Amp;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @template TValue
|
|
|
|
* @template TSend
|
|
|
|
* @template TReturn
|
2020-11-10 13:28:43 -06:00
|
|
|
*
|
|
|
|
* @template-implements Pipeline<TValue>
|
|
|
|
* @template-implements \IteratorAggregate<int, TValue>
|
2020-05-13 10:15:21 -05:00
|
|
|
*/
|
2020-11-10 12:05:47 -06: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
|
|
|
|
2020-09-24 22:14:58 -05:00
|
|
|
/** @var Promise<TReturn> */
|
|
|
|
private Promise $promise;
|
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
|
|
|
*
|
2020-05-19 10:49:40 -05:00
|
|
|
* @throws \Error Thrown if the callable throws any exception.
|
2020-05-18 17:01:14 -05:00
|
|
|
* @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
|
|
|
|
2020-05-19 10:49:40 -05:00
|
|
|
try {
|
2020-09-24 22:14:58 -05:00
|
|
|
$generator = $callable(...$args);
|
2020-05-19 10:49:40 -05:00
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
throw new \Error("The callable threw an exception", 0, $exception);
|
|
|
|
}
|
2020-05-13 10:15:21 -05:00
|
|
|
|
2020-08-28 11:51:43 -05:00
|
|
|
if (!$generator instanceof \Generator) {
|
2020-05-13 10:15:21 -05:00
|
|
|
throw new \TypeError("The callable did not return a Generator");
|
|
|
|
}
|
2020-08-28 11:51:43 -05:00
|
|
|
|
2020-09-24 22:14:58 -05:00
|
|
|
$this->promise = async(static function () use ($generator, $source): mixed {
|
|
|
|
$yielded = $generator->current();
|
|
|
|
|
|
|
|
while ($generator->valid()) {
|
|
|
|
try {
|
2020-10-02 14:14:30 -05:00
|
|
|
$yielded = $generator->send(await($source->emit($yielded)));
|
2020-09-25 12:57:56 -05:00
|
|
|
} catch (DisposedException $exception) {
|
|
|
|
throw $exception; // Destroys generator and fails pipeline.
|
2020-09-24 22:14:58 -05:00
|
|
|
} catch (\Throwable $exception) {
|
|
|
|
$yielded = $generator->throw($exception);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $generator->getReturn();
|
|
|
|
});
|
|
|
|
|
2020-09-25 12:57:56 -05:00
|
|
|
$this->promise->onResolve(static function (?\Throwable $exception) use ($source): void {
|
2020-08-28 11:51:43 -05:00
|
|
|
if ($source->isDisposed()) {
|
|
|
|
return; // AsyncGenerator object was destroyed.
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($exception) {
|
|
|
|
$source->fail($exception);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$source->complete();
|
|
|
|
});
|
2020-05-19 10:49:40 -05:00
|
|
|
}
|
2020-05-13 10:15:21 -05:00
|
|
|
|
2020-05-19 10:49:40 -05:00
|
|
|
public function __destruct()
|
|
|
|
{
|
2020-08-27 12:45:48 -05:00
|
|
|
$this->source->destroy();
|
2020-05-13 16:48:38 -05:00
|
|
|
}
|
|
|
|
|
2020-05-13 10:15:21 -05:00
|
|
|
/**
|
2020-05-14 14:14:54 -05:00
|
|
|
* @inheritDoc
|
2020-11-10 13:28:43 -06:00
|
|
|
*
|
|
|
|
* @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
|
|
|
{
|
2020-05-19 10:49:40 -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
|
|
|
|
*
|
2020-08-23 09:18:28 -05:00
|
|
|
* @return Promise<mixed|null> Resolves with null if the pipeline has completed.
|
2020-05-18 13:49:56 -05:00
|
|
|
*
|
2020-05-21 10:11:22 -05:00
|
|
|
* @psalm-return Promise<TValue|null>
|
2020-05-18 17:01:14 -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-25 12:57:56 -05:00
|
|
|
public function send(mixed $value): mixed
|
2020-05-13 10:15:21 -05:00
|
|
|
{
|
2020-05-19 10:49:40 -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.
|
|
|
|
*
|
2020-08-23 09:18:28 -05:00
|
|
|
* @return Promise<mixed|null> Resolves with null if the pipeline has completed.
|
2020-05-18 17:01:14 -05:00
|
|
|
*
|
2020-05-21 10:11:22 -05:00
|
|
|
* @psalm-return Promise<TValue|null>
|
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
|
|
|
{
|
2020-05-19 10:49:40 -05:00
|
|
|
return $this->source->throw($exception);
|
2020-05-13 10:15:21 -05:00
|
|
|
}
|
|
|
|
|
2020-05-13 14:59:31 -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
|
2020-05-13 14:59:31 -05:00
|
|
|
{
|
2020-05-19 10:49:40 -05:00
|
|
|
$this->source->dispose();
|
2020-05-13 14:59:31 -05:00
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2020-09-24 22:14:58 -05:00
|
|
|
return await($this->promise);
|
2020-05-13 10:15:21 -05:00
|
|
|
}
|
2020-11-10 12:05:47 -06:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritDoc
|
2020-11-10 13:28:43 -06:00
|
|
|
*
|
2020-11-10 21:56:00 -06:00
|
|
|
* @paslm-return \Traversable<int, TValue>
|
2020-11-10 12:05:47 -06:00
|
|
|
*/
|
2020-11-10 21:56:00 -06:00
|
|
|
public function getIterator(): \Traversable
|
2020-11-10 12:05:47 -06:00
|
|
|
{
|
2020-11-10 13:28:43 -06:00
|
|
|
while (null !== $value = $this->source->continue()) {
|
2020-11-10 12:05:47 -06:00
|
|
|
yield $value;
|
|
|
|
}
|
|
|
|
}
|
2020-05-13 10:15:21 -05:00
|
|
|
}
|