Menu

Reference / Async

Once

finalclass
final class Once

Run something exactly once, however many tasks ask for it.

The lazy initialiser: a connection pool, a parsed config. It exists because the work suspends; without it two tasks both see "not ready yet" and both build. Everyone who calls run() gets the same value, or the same error. Callers that arrive while the initialiser is running wait for it.

$pool = $once->run(fn() => buildPool());

A cancelled initialiser is not a result: the state resets so the next caller can try again instead of inheriting someone else's cancellation. A failure other than cancellation is remembered and rethrown to every later caller.

Example

use Async\Once;
use function Async\async;
use function Async\awaitAll;
use function Async\delay;
use function Async\spawn;

async(function () {
    $once = new Once();
    $built = 0;

    $get = function () use ($once, &$built) {
        // The initialiser suspends, so without Once every caller would build.
        return $once->run(function () use (&$built) {
            $built++;
            delay(0.01);
            return "connection";
        });
    };

    $results = awaitAll(spawn($get), spawn($get), spawn($get));
    echo implode(",", $results), "\n";
    echo "built $built time(s), hasRun: ", $once->hasRun() ? "yes" : "no", "\n";
});

Output

connection,connection,connection
built 1 time(s), hasRun: yes

See also: Mutex

Methods

hasRun()Whether the initialiser has finished, with a value or with an error.
run()Run $fn unless it already ran, and return its result.

hasRun()#source

function hasRun(): bool

Whether the initialiser has finished, with a value or with an error.

Returns

bool โ€” true once finished; false while fresh, running or after a cancelled attempt

run()#source

function run(callable $fn): mixed

Run $fn unless it already ran, and return its result.

The first caller runs $fn; concurrent callers wait and receive its result. Later calls return the stored value, or rethrow the stored error, without calling $fn again.

Parameters

$fn callable
the initialiser, called with no arguments at most once

Returns

mixed โ€” the value $fn returned

Throws

\Throwable
whatever $fn threw, to every caller
CancelledException
when the running initialiser or this waiting task is cancelled

Example

use Async\Once;
use function Async\async;
use function Async\awaitAll;
use function Async\delay;
use function Async\spawn;

async(function () {
    $once = new Once();
    $built = 0;

    $get = function () use ($once, &$built) {
        // The initialiser suspends, so without Once every caller would build.
        return $once->run(function () use (&$built) {
            $built++;
            delay(0.01);
            return "connection";
        });
    };

    $results = awaitAll(spawn($get), spawn($get), spawn($get));
    echo implode(",", $results), "\n";
    echo "built $built time(s), hasRun: ", $once->hasRun() ? "yes" : "no", "\n";
});

Output

connection,connection,connection
built 1 time(s), hasRun: yes

View source