AbortController-mønstre
AbortController og AbortSignal er fulde WHATWG-implementeringer i Phasis. Standardens kompositionshjælpere (AbortSignal.any, AbortSignal.timeout) følger også med. Denne side gennemgår de mønstre, du faktisk bruger, og hvordan annullering føres videre på tværs af grænsen PHP.
Annullering af et fetch-kald
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Årsagen er som standard AbortError, men du kan angive din egen:
ctrl.abort(new Error('user cancelled'));
fetch afviser med den værdi, du sendte.
Timeout
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) returnerer et signal, der afbryder sig selv efter ms. Timeren udløses via motorens event loop, det samme som driver setTimeout / queueMicrotask (se API: Event loop). Fetch-transporten aflæser signalet under overførslen — for standardtransporten CurlTransport via CURLOPT_PROGRESSFUNCTION — så anmodningen afbrydes hurtigt, når fristen udløber.
Løkken bruger som standard virtuel tid: den flytter sit ur frem til den næste ventende frist i stedet for at sove PHP, så et fetch(url, { signal: AbortSignal.timeout(5000) }) enten afsluttes før fristen eller afbrydes ved næste løkketakt — den får aldrig PHP til at vente. Realtidstiming kræver, at værten driver tickEventLoop() efter et vægur.
Race: den første annullering vinder
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
Videregivelse gennem lagene
Pak dit eget asynkrone arbejde ind i en signalbevidst løkke:
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;
}
Fetch-kaldet arver signalet direkte; den manuelle if (signal.aborted) dækker mellemrummene mellem anmodninger, hvor sideløkken ellers ville fortsætte.
Lytning efter abort-hændelsen
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason sættes, før lytterne kaldes.
Signal i en brugerdefineret fetch-transport
Hvis du udskifter transporten via setFetchTransport(), sendes signalet til din callable som en JsObject. Respektér det ved at aflæse det:
$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',
);
}
// ...
});
Hvis du kaster TransportException med kind: 'aborted', afviser JS-sidens fetch med AbortError DOMException — det samme som hvis specifikationens abort-løkke var blevet kørt.
For backends med indbygget annullering (Guzzles on_progress, Symfonys ResponseInterface::cancel) skal du tilkoble den samme aflæs-og-kast-struktur.
Mønstre, der endnu ikke virker
signal.addEventListener('abort', () => fetch(cleanupUrl))— lytterne kaldes synkront; oprydnings-fetch-kaldet starter, men bliver ikke færdigt under abort-dispatchen. Planlæg det via den omgivendeawait.
setTimeout(() => ctrl.abort(), N) virker (timerfamilien følger med og er dækket af WPT), men AbortSignal.timeout(N) er kortere og udtrykker hensigten tydeligere, når du blot skal bruge et signal, der afbryder efter en frist.
Se også
- Fetch-transport — komplet grænseflade for anmodning/svar/signal til brugerdefinerede transporter.
- Web-API'er (WPT) — beståelsesprocenten for AbortController/AbortSignal i WPT.