namespace Psl\Async; use Closure; use Generator; use Psl\Async\Internal\AwaitableIterator; use Psl\Async\Internal\State; use Psl\Promise\PromiseInterface; use Revolt\EventLoop; use Throwable; use function is_array; final class Awaitable implements PromiseInterface { /** * @internal Use {@see Deferred} to create and resolve an awaitable. */ public function __construct( // v- `static` is used on purpose here to make sure the parser fails. private static readonly State $state ) {} /** * Iterate over the given `Awaitable`s in completion order. */ public static function iterate(dict> $awaitables): Generator, void, void> { $iterator = new AwaitableIterator::(); foreach $awaitables as $key => $awaitable { $iterator->enqueue($awaitable->state, $key, $awaitable); } $iterator->complete(); while ($k, $v) = $iterator->consume() { yield $k => $v; } } public static function complete(T $result): Awaitable { $state = new State::(); $state->complete($result); new Awaitable::($state) } public static function error(Throwable $throwable): Awaitable { $state = new State::(); $state->error($throwable); new Awaitable::($state) } /** * @return bool True if the operation has completed. */ public function isComplete(): bool { return $this->state->isComplete(); } /** * {@inheritDoc} */ public function then(Closure<(T), S> $success, Closure<(Throwable), S> $failure): Awaitable { $state = new State::(); $this->state->subscribe( static function (?Throwable $error, ?S $value) use ($state, $success, $failure): void { if $error !== null { try { $state->complete($failure($error)); } catch (Throwable $throwable) { $state->error($throwable); } return; } try { // @ara-suppress: we know that $value is not null here, but the type system doesn't, and can't know. $state->complete($success($value)); } catch (Throwable $throwable) { $state->error($throwable); } }, ); return new Awaitable::($state); } /** * {@inheritDoc} */ public function map(Closure<(T), S> $success): Awaitable { return $this->then::($success, static fn (Throwable $throwable): S => throw $throwable); } /** * {@inheritDoc} */ public function catch(Closure<(Throwable), S> $failure): Awaitable { return $this->then::( static fn(T $value): T|S => $value, static fn(Throwable $throwable): T|S => $failure($throwable), ); } /** * {@inheritDoc} */ public function always(Closure<(), void> $always): Awaitable { $state = new State::(); $this->state->subscribe(static function (?Throwable $error, ?T $value) use ($state, $always): void { try { $always(); if ($error) { $state->error($error); } else { // @ara-suppress: we know that $value is not null here, but the type system doesn't, and can't know. $state->complete($value); } } catch (Throwable $throwable) { $state->error($throwable); } }); return new Awaitable::($state); } /** * Awaits the operation to complete. * * Throws a `Throwable` if the operation fails. */ public function await(): T { $suspension = EventLoop::getSuspension::(); $this->state->subscribe( static function (?Throwable $error, ?T $value) use ($suspension): void { if ($error) { $suspension->throw($error); } else { $suspension->resume($value); } }, ); $suspension->suspend() } /** * Do not forward unhandled errors to the event loop handler. */ public function ignore(): self { $this->state->ignore(); return $this; } }