Vzory použití AbortController
AbortController a AbortSignal jsou v Phasis plně implementovány podle WHATWG. K dispozici jsou i standardní pomocné funkce pro skládání (AbortSignal.any, AbortSignal.timeout). Tato stránka popisuje běžně používané vzory a šíření zrušení přes hranici PHP.
Zrušení požadavku fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Výchozím důvodem je AbortError, ale můžete zadat vlastní:
ctrl.abort(new Error('user cancelled'));
fetch vyvolá zamítnutí s předanou hodnotou.
Časový limit
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) vrací signál, který se sám zruší po ms. Časovač se spouští prostřednictvím smyčky událostí enginu, tedy stejné smyčky, která řídí setTimeout / queueMicrotask (viz API: Smyčka událostí). Fetch transport během přenosu signál kontroluje — u výchozího CurlTransport prostřednictvím CURLOPT_PROGRESSFUNCTION — takže se požadavek po uplynutí lhůty rychle zruší.
Smyčka ve výchozím nastavení používá virtuální čas: místo skutečného čekání PHP posune hodiny k nejbližší nevyřízené lhůtě, takže se fetch(url, { signal: AbortSignal.timeout(5000) }) buď dokončí před lhůtou, nebo se zruší při dalším kroku smyčky — nikdy nenechá PHP čekat. Pro běh v reálném čase musí hostitel řídit tickEventLoop() podle skutečných hodin.
Závod: vyhrává první zrušení
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
Šíření mezi vrstvami
Obalte vlastní asynchronní práci smyčkou, která reaguje na signál:
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;
}
Požadavek fetch signál zdědí přímo; ruční if (signal.aborted) pokrývá prodlevy mezi požadavky, kdy by jinak smyčka stránky pokračovala.
Naslouchání události zrušení
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason se nastaví ještě před vyvoláním posluchačů.
Signál ve vlastním fetch transportu
Pokud přes setFetchTransport() vyměníte transport, signál se předá vaší volatelné funkci jako JsObject. Respektujte ho pravidelnou kontrolou:
$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',
);
}
// ...
});
Vyvoláním TransportException s kind: 'aborted' způsobíte, že JS-ová strana fetch odmítne AbortError DOMException — stejně, jako kdyby se spustila smyčka rušení podle specifikace.
U backendů s nativním rušením (Guzzle's on_progress, Symfony's ResponseInterface::cancel) použijte stejný vzor kontroly a vyvolání chyby.
Vzory, které zatím nefungují
signal.addEventListener('abort', () => fetch(cleanupUrl))— posluchač se spouští synchronně; úklidový požadavek fetch začne, ale během zpracování události zrušení se nedokončí. Naplánujte ho přes okolníawait.
setTimeout(() => ctrl.abort(), N) funguje (sada časovačů je součástí implementace a pokrývají ji WPT), ale AbortSignal.timeout(N) je kratší a lépe vystihuje záměr, pokud potřebujete jen signál, který se zruší po uplynutí lhůty.
Viz také
- Fetch transport — úplné rozhraní požadavků, odpovědí a signálů pro vlastní transporty.
- Webová API (WPT) — míra úspěšnosti testů WPT pro AbortController/AbortSignal.