diff --git a/src/Once.php b/src/Once.php index 1c1b24e..44f54a8 100644 --- a/src/Once.php +++ b/src/Once.php @@ -20,15 +20,17 @@ final class Once private ?Future $future = null; /** - * @var ?T + * @var T */ - private mixed $value = null; + private mixed $value; /** * @var \Closure(T): bool */ private readonly mixed $isAlive; + private bool $isResolved = false; + /** * @param \Closure(): T $function * @param ?\Closure(T): bool $isAlive @@ -45,16 +47,22 @@ public function __construct( */ public function await(?Cancellation $cancellation = null): mixed { - if ($this->value !== null && ($this->isAlive)($this->value)) { + if ($this->isResolved && ($this->isAlive)($this->value)) { return $this->value; } + $this->isResolved = false; + $this->future ??= async($this->function); try { - return $this->value = $this->future->await($cancellation); + $this->value = $this->future->await($cancellation); } finally { $this->future = null; } + + $this->isResolved = true; + + return $this->value; } } diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/tests/OnceTest.php b/tests/OnceTest.php new file mode 100644 index 0000000..62d44a7 --- /dev/null +++ b/tests/OnceTest.php @@ -0,0 +1,70 @@ + */ + $deferred = new DeferredFuture(); + $once = new Once(static function () use ($deferred): string { + $deferred->getFuture()->await(); + + return random_bytes(8); + }); + $future1 = async(static fn() => $once->await()); + $future2 = async(static fn() => $once->await()); + + async(static function () use ($deferred, $future1, $future2): void { + self::assertFalse($future1->isComplete()); + self::assertFalse($future2->isComplete()); + + $deferred->complete(); + + self::assertSame($future1->await(), $future2->await()); + })->await(); + } + + public function testWorksWithNull(): void + { + /** @var DeferredFuture */ + $deferred = new DeferredFuture(); + $once = new Once(static fn(): null => $deferred->getFuture()->await()); + $future = async(static fn() => $once->await()); + + async(static function () use ($deferred, $future): void { + self::assertFalse($future->isComplete()); + + $deferred->complete(); + + self::assertNull($future->await()); + })->await(); + } + + public function testIsAlive(): void + { + $once = new Once( + static function (): int { + /** @var int */ + static $i = 0; + + return ++$i; + }, + static fn(int $i): bool => $i > 1, + ); + + self::assertSame(1, $once->await()); + self::assertSame(2, $once->await()); + self::assertSame(2, $once->await()); + self::assertSame(2, $once->await()); + } +}