Vzory používania AbortController
AbortController a AbortSignal sú plnohodnotné implementácie WHATWG v Phasis. Súčasťou sú aj štandardné pomocné funkcie na skladanie (AbortSignal.any, AbortSignal.timeout). Táto stránka sa venuje vzorom, ktoré budete skutočne používať, a tomu, ako sa zrušenie šíri cez hranicu PHP.
Zrušenie požiadavky fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Dôvodom je predvolene AbortError, môžete však zadať vlastný:
ctrl.abort(new Error('user cancelled'));
fetch zamietne požiadavku s ľubovoľnou odovzdanou hodnotou.
Časový limit
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) vráti signál, ktorý sa zruší po uplynutí ms. Časovač sa spustí cez slučku udalostí enginu, tú istú, ktorá riadi setTimeout / queueMicrotask (pozrite API: Slučka udalostí). Transport fetch kontroluje signál počas prenosu — pri predvolenom CurlTransport prostredníctvom CURLOPT_PROGRESSFUNCTION — takže požiadavka sa zruší hneď po uplynutí časového limitu.
Slučka štandardne používa virtuálny čas: posunie hodiny na najbližší čakajúci termín namiesto toho, aby skutočne čakala PHP. fetch(url, { signal: AbortSignal.timeout(5000) }) sa preto buď dokončí pred termínom, alebo sa zruší pri nasledujúcom kroku slučky — PHP nikdy nemusí čakať. Ak chcete riadenie v reálnom čase, hostiteľ musí spúšťať tickEventLoop() podľa hodín reálneho času.
Súťaž: vyhráva prvé zrušenie
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
Šírenie cez vrstvy
Obaľte vlastnú asynchrónnu prácu slučkou, ktorá 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žiadavka fetch preberá signál priamo; manuálny if (signal.aborted) pokrýva medzery medzi požiadavkami, počas ktorých by slučka stránky inak pokračovala.
Počúvanie udalosti zrušenia
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason sa nastaví pred spustením poslucháčov.
Signál vo vlastnom transporte fetch
Ak zmeníte transport cez setFetchTransport(), volateľný objekt dostane signál ako JsObject. Rešpektujte ho kontrolovaním:
$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',
);
}
// ...
});
Vyvolaním TransportException s kind: 'aborted' dosiahnete, že JS-strana fetch zamietne požiadavku s AbortError DOMException — rovnako, ako keby sa spustila špecifikačná slučka rušenia.
V prípade backendov s natívnym rušením (Guzzle on_progress, Symfony ResponseInterface::cancel) použite rovnaký vzor kontroly a vyvolania chyby.
Vzory, ktoré zatiaľ nefungujú
signal.addEventListener('abort', () => fetch(cleanupUrl))— poslucháče sa spúšťajú synchrónne; požiadavka fetch na upratanie sa začne, ale počas spracovania udalosti zrušenia sa nedokončí. Naplánujte ju cez nadradenéawait.
setTimeout(() => ctrl.abort(), N) funguje (skupina časovačov je súčasťou implementácie a pokrývajú ju testy WPT), ale AbortSignal.timeout(N) je stručnejšie a lepšie vystihuje zámer, keď potrebujete iba signál na zrušenie po uplynutí časového limitu.
Pozrite si aj
- Transport fetch — úplné rozhranie požiadaviek, odpovedí a signálov pre vlastné transporty.
- Webové API (WPT) — miera úspešnosti testov WPT pre AbortController/AbortSignal.