await

(PHP 8.6+, True Async 1.0)

await() — Wartet auf den Abschluss einer Coroutine, eines Async\Future oder eines anderen Async\Completable. Gibt das Ergebnis zurueck oder wirft eine Ausnahme.

Beschreibung

php
await(Async\Completable $awaitable, ?Async\Completable $cancellation = null): mixed

Unterbricht die Ausfuehrung der aktuellen Coroutine, bis das angegebene Async\Completable $awaitable abgeschlossen ist (oder bis $cancellation ausgeloest wird, falls angegeben) und gibt das Ergebnis zurueck. Wenn das awaitable bereits abgeschlossen ist, wird das Ergebnis sofort zurueckgegeben.

Wenn die Coroutine mit einer Ausnahme beendet wurde, wird diese an den aufrufenden Code weitergegeben.

Parameter

awaitable Ein Objekt, das das Async\Completable-Interface implementiert (erweitert Async\Awaitable). Typischerweise ist dies:

  • Async\Coroutine - das Ergebnis eines spawn()-Aufrufs
  • Async\TaskGroup - eine Aufgabengruppe
  • Async\Future - ein zukuenftiger Wert

cancellation Ein optionales Async\Completable-Objekt; wenn es abgeschlossen wird, wird das Warten abgebrochen.

Rueckgabewerte

Gibt den Wert zurueck, den die Coroutine zurueckgegeben hat. Der Rueckgabetyp haengt von der Coroutine ab.

Fehler/Ausnahmen

Wenn die Coroutine mit einer Ausnahme beendet wurde, wirft await() diese Ausnahme erneut.

Wenn die Coroutine abgebrochen wurde, wird Async\AsyncCancellation geworfen.

Wenn der Abbruch-Token ($cancellation) ausgelöst wird, wird Async\OperationCanceledException ausgelöst. Die ursprüngliche Ausnahme des Tokens ist über $e->getPrevious() verfügbar. So lässt sich unterscheiden, ob der Token ausgelöst wurde oder das Awaitable-Objekt selbst eine Ausnahme geworfen hat.

Wie die Ausnahme zugestellt wird

Wenn eine Coroutine mit einer Ausnahme endet, bleibt das Ergebnis auf ihrem Handle liegen, bis es jemand abholt. Das Verhalten ist symmetrisch zu Async\Future und hängt davon ab, ob außer dem Scheduler noch jemand das Coroutine-Handle hält:

  • Das Handle wird gehalten ($coro = spawn(...), die Coroutine liegt in einem Array, wurde an await_all() übergeben usw.) — die Ausnahme bleibt auf dem Handle und wartet. Jeder spätere await($coro) bekommt sie, auch wenn die Coroutine längst beendet ist.
  • Das Handle wird von niemandem gehalten (Fire-and-Forget — spawn(...) ohne das Ergebnis zu speichern) — die Ausnahme erscheint beim Zerstören des Handles über das Fire-and-Forget Safety Net.

Die wichtigste praktische Konsequenz: await fängt die Ausnahme auch bei einem Race:

php
use function Async\spawn;
use function Async\await;

$coro = spawn(function () {
    throw new RuntimeException('boom');
});

// Die Coroutine darf früher fertig sein als wir am await ankommen — das ist okay.
// Die Ausnahme wartet hier in Ruhe auf uns:
try {
    await($coro);
} catch (RuntimeException $e) {
    echo "gefangen: ", $e->getMessage(), "\n"; // gefangen: boom
}

Dasselbe gilt für await_all(), await_any_or_fail() und andere await_*(): Sie können Coroutinen in einem Array sammeln, sie parallel arbeiten lassen und sie später abwarten. Die Ausnahmen werden über await eingesammelt.

Wenn ein Parent-Scope vor seinen Coroutinen stirbt, bekommen die Child-Coroutinen laut Spezifikation eine AsyncCancellation. Dieser Pfad wird separat behandelt und hängt nicht davon ab, wer das Handle hält.

Beispiele

Beispiel #1 Grundlegende Verwendung von await()

php
<?php
use function Async\spawn;
use function Async\await;

$coroutine = spawn(function() {
    return "Hallo, Async!";
});

echo await($coroutine); // Hallo, Async!
?>

Beispiel #2 Sequentielles Warten

php
<?php
use function Async\spawn;
use function Async\await;

function fetchUser(int $id): array {
    return json_decode(
        file_get_contents("https://api/users/$id"),
        true
    );
}

function fetchPosts(int $userId): array {
    return json_decode(
        file_get_contents("https://api/posts?user=$userId"),
        true
    );
}

$userCoro = spawn(fetchUser(...), 123);
$user = await($userCoro);

$postsCoro = spawn(fetchPosts(...), $user['id']);
$posts = await($postsCoro);

echo "Benutzer: {$user['name']}\n";
echo "Beitraege: " . count($posts) . "\n";
?>

Beispiel #3 Ausnahmebehandlung

php
<?php
use function Async\spawn;
use function Async\await;

$coroutine = spawn(function() {
    $response = file_get_contents('https://api.com/data');

    if ($response === false) {
        throw new RuntimeException("Daten konnten nicht abgerufen werden");
    }

    return $response;
});

try {
    $data = await($coroutine);
    echo "Daten empfangen\n";
} catch (RuntimeException $e) {
    echo "Fehler: " . $e->getMessage() . "\n";
}
?>

Beispiel #4 await mit TaskGroup

php
<?php
use function Async\spawn;
use function Async\await;
use Async\TaskGroup;

$taskGroup = new TaskGroup();

$taskGroup->spawn(function() {
    return "Ergebnis 1";
});

$taskGroup->spawn(function() {
    return "Ergebnis 2";
});

$taskGroup->spawn(function() {
    return "Ergebnis 3";
});

// Array aller Ergebnisse abrufen
$results = await($taskGroup);
print_r($results); // Array der Ergebnisse
?>

Beispiel #5 Mehrfaches await auf dieselbe Coroutine

php
<?php
use function Async\spawn;
use function Async\await;

$coroutine = spawn(function() {
    Async\timeout(1000);
    return "Fertig";
});

// Das erste await wartet auf das Ergebnis
$result1 = await($coroutine);
echo "$result1\n";

// Nachfolgende awaits geben das Ergebnis sofort zurueck
$result2 = await($coroutine);
echo "$result2\n";

var_dump($result1 === $result2); // true
?>

Beispiel #6 await innerhalb einer Coroutine

php
<?php
use function Async\spawn;
use function Async\await;

spawn(function() {
    echo "Eltern-Coroutine gestartet\n";

    $child = spawn(function() {
        echo "Kind-Coroutine laeuft\n";
        Async\delay(1000);
        return "Ergebnis vom Kind";
    });

    echo "Warte auf Kind...\n";
    $result = await($child);
    echo "Empfangen: $result\n";
});

echo "Hauptcode wird fortgesetzt\n";
?>

Changelog

VersionBeschreibung
1.0.0Funktion await() hinzugefuegt

Siehe auch