Mönster för AbortController
AbortController och AbortSignal är fullständiga WHATWG-implementationer i Phasis. Standardhjälpfunktionerna för komposition (AbortSignal.any, AbortSignal.timeout) ingår också. Den här sidan går igenom de mönster du faktiskt använder och hur avbrott sprids över gränsen till PHP.
Avbryta en fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Orsaken är AbortError som standard, men du kan ange en egen:
ctrl.abort(new Error('user cancelled'));
fetch avvisar med det värde du skickade in.
Tidsgräns
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) returnerar en signal som avbryter sig själv efter ms. Timern utlöses via motorns händelseloop, samma loop som driver setTimeout / queueMicrotask (se API: Händelseloop). Fetch-transporten läser av signalen under överföringen — för standardtransporten CurlTransport via CURLOPT_PROGRESSFUNCTION — så begäran avbryts snabbt när tidsgränsen nås.
Loopen använder virtuell tid som standard: den flyttar klockan fram till nästa väntande tidsgräns i stället för att vänta PHP, så en fetch(url, { signal: AbortSignal.timeout(5000) }) antingen slutförs före tidsgränsen eller avbryts vid nästa loopvarv — den låter aldrig PHP vänta. För att hålla jämn takt i realtid måste värden driva tickEventLoop() mot en väggklocka.
Kapplöpning: den första avbrytningen vinner
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
Propagering genom lager
Omslut ditt eget asynkrona arbete med en signalmedveten loop:
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;
}
Hämtningen ärver signalen direkt; den manuella if (signal.aborted) täcker luckor mellan förfrågningar då sidloopen annars skulle fortsätta köra.
Lyssna efter avbrytningshändelsen
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason är inställd innan lyssnarna anropas.
Signal i en anpassad fetch-transport
Om du byter transport via setFetchTransport() skickas signalen till din anropsbara funktion som en JsObject. Hantera den genom att kontrollera den regelbundet:
$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',
);
}
// ...
});
Om du kastar TransportException med kind: 'aborted' avvisas JS-sidans fetch med AbortError DOMException — precis som om specifikationens avbrytningsloop hade körts.
För backendlösningar med inbyggt stöd för avbrytning (Guzzles on_progress, Symfonys ResponseInterface::cancel), använd samma mönster med kontroll och kastat undantag.
Mönster som ännu inte fungerar
signal.addEventListener('abort', () => fetch(cleanupUrl))— lyssnare anropas synkront; rensningshämtningen startar men hinner inte slutföras under avbrytningshanteringen. Schemalägg den via den omgivandeawait.
setTimeout(() => ctrl.abort(), N) fungerar (timerfunktionerna ingår och täcks av WPT), men AbortSignal.timeout(N) är kortare och uttrycker avsikten bättre när du bara vill ha en signal som avbryter efter en tidsgräns.
Se även
- Fetch-transport — komplett gränssnitt för begäran/svar/signal för anpassade transporter.
- Web APIs (WPT) — godkännandefrekvens för AbortController/AbortSignal i WPT.