AbortController-Muster
AbortController und AbortSignal sind vollständige WHATWG-Implementierungen in Phasis. Auch die standardmäßigen Kompositionshelfer (AbortSignal.any, AbortSignal.timeout) sind enthalten. Diese Seite behandelt gängige Muster und erklärt, wie sich ein Abbruch über die PHP-Grenze hinweg fortpflanzt.
Einen Fetch abbrechen
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Der Grund ist standardmäßig AbortError, du kannst aber einen eigenen angeben:
ctrl.abort(new Error('user cancelled'));
fetch wird mit dem übergebenen Wert zurückgewiesen.
Timeout
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) gibt ein Signal zurück, das sich nach ms selbst abbricht. Der Timer wird über die Ereignisschleife der Engine ausgelöst, dieselbe, die auch setTimeout / queueMicrotask ausführt (siehe API: Ereignisschleife). Der Fetch-Transport fragt das Signal während der Übertragung ab – beim standardmäßigen CurlTransport über CURLOPT_PROGRESSFUNCTION –, sodass die Anfrage sofort nach Ablauf der Frist abgebrochen wird.
Die Schleife verwendet standardmäßig virtuelle Zeit: Sie stellt ihre Uhr auf die nächste anstehende Frist vor, statt PHP zu schlafen. Ein fetch(url, { signal: AbortSignal.timeout(5000) }) wird daher entweder vor der Frist abgeschlossen oder beim nächsten Schleifendurchlauf abgebrochen – PHP muss nie warten. Für die Taktung nach Echtzeit muss der Host tickEventLoop() anhand einer realen Uhr ausführen.
Wettlauf: Der erste Abbruch gewinnt
const user = new AbortController();
const signal = AbortSignal.any([user.signal, AbortSignal.timeout(10_000)]);
const res = await fetch(url, { signal });
// res rejects if EITHER the user clicks cancel OR 10s elapses
Weitergabe über mehrere Ebenen
Umschließe deine eigene asynchrone Arbeit mit einer signalbewussten Schleife:
async function fetchPaged(url, signal) {
const out = [];
for (let page = 1; ; page++) {
if (signal.aborted) throw signal.reason;
const r = await fetch(`${url}?page=${page}`, { signal });
const data = await r.json();
if (data.length === 0) break;
out.push(...data);
}
return out;
}
Der Fetch übernimmt das Signal direkt; das manuelle if (signal.aborted) deckt die Lücken zwischen Anfragen ab, in denen die Seitenschleife sonst weiterlaufen würde.
Auf das Abbruchereignis reagieren
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason wird gesetzt, bevor Listener ausgelöst werden.
Signal in einem benutzerdefinierten Fetch-Transport
Wenn du den Transport über setFetchTransport() austauschst, wird das Signal als JsObject an deine aufrufbare Funktion übergeben. Frage es regelmäßig ab, um darauf zu reagieren:
$engine->setFetchTransport(function (array $req, $signal) {
$ch = curl_init($req['url']);
// ...
if ($signal !== null) {
curl_setopt($ch, CURLOPT_PROGRESSFUNCTION,
fn () => $signal->get('aborted')->value ? 1 : 0);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
}
$body = curl_exec($ch);
if (curl_errno($ch) === CURLE_ABORTED_BY_CALLBACK) {
throw new \Phasis\BuiltIn\Fetch\TransportException(
'aborted', 'aborted',
);
}
// ...
});
Wenn du TransportException mit kind: 'aborted' auslöst, wird das JS-seitige fetch mit AbortError DOMException zurückgewiesen – so, als wäre die Abbruchschleife der Spezifikation ausgeführt worden.
Bei Backends mit nativer Abbruchfunktion (on_progress von Guzzle, ResponseInterface::cancel von Symfony) verwendest du dieselbe Abfrage-und-Auslöse-Logik.
Muster, die noch nicht funktionieren
signal.addEventListener('abort', () => fetch(cleanupUrl))– Listener werden synchron ausgelöst. Der Aufräum-Fetch startet, wird aber nicht während der Abbruchverarbeitung abgeschlossen. Plane ihn über die umgebendeawaitein.
setTimeout(() => ctrl.abort(), N) funktioniert (die Timer-Familie ist enthalten und durch WPT abgedeckt), aber AbortSignal.timeout(N) ist kürzer und beschreibt die Absicht besser, wenn du nur ein Signal möchtest, das nach einer Frist abbricht.
Siehe auch
- Fetch-Transport – vollständige Request-, Response- und Signal-Schnittstelle für benutzerdefinierte Transporte.
- Web-APIs (WPT) – Bestehensquote der AbortController/AbortSignal-WPT-Tests.