Wzorce AbortController
AbortController i AbortSignal to pełne implementacje WHATWG w Phasis. Dostępne są również standardowe funkcje kompozycji (AbortSignal.any, AbortSignal.timeout). Ta strona omawia wzorce, po które faktycznie sięgasz, oraz sposób propagacji anulowania przez granicę PHP.
Anulowanie fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
Domyślnie powodem jest AbortError, ale możesz podać własny:
ctrl.abort(new Error('user cancelled'));
fetch odrzuca obietnicę z przekazaną wartością.
Limit czasu
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) zwraca sygnał, który sam się anuluje po upływie ms. Timer uruchamia się przez pętlę zdarzeń silnika, tę samą, która obsługuje setTimeout / queueMicrotask (zobacz API: Pętla zdarzeń). Transport fetch sprawdza sygnał podczas transferu — w przypadku domyślnego CurlTransport za pośrednictwem CURLOPT_PROGRESSFUNCTION — więc żądanie jest szybko anulowane po upływie terminu.
Pętla domyślnie działa w czasie wirtualnym: przesuwa zegar do następnego oczekującego terminu zamiast czekać PHP, więc fetch(url, { signal: AbortSignal.timeout(5000) }) albo kończy działanie przed upływem terminu, albo zostaje przerwane przy kolejnym tyknięciu pętli — nigdy nie każe PHP czekać. Aby sterować tempem w czasie rzeczywistym, host musi prowadzić tickEventLoop() według zegara ściennego.
Wyścig: pierwsze anulowanie wygrywa
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
Propagacja przez kolejne warstwy
Opakuj własną pracę asynchroniczną w pętlę reagującą na sygnał:
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 dziedziczy sygnał bezpośrednio; ręczne if (signal.aborted) obejmuje przerwy między żądaniami, podczas których pętla strony działałaby dalej.
Nasłuchiwanie zdarzenia przerwania
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason jest ustawiane przed wywołaniem nasłuchujących.
Sygnał we własnym transporcie fetch
Jeśli zmienisz transport przez setFetchTransport(), sygnał zostanie przekazany wywoływanej funkcji jako JsObject. Obsłuż go, sprawdzając okresowo:
$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',
);
}
// ...
});
Rzucenie TransportException z kind: 'aborted' sprawia, że JS po stronie fetch odrzuca z AbortError DOMException — tak samo, jak gdyby zadziałała pętla przerwania ze specyfikacji.
W przypadku backendów z natywnym anulowaniem (Guzzle's on_progress, Symfony's ResponseInterface::cancel) zastosuj ten sam schemat sprawdzania i rzucania wyjątku.
Wzorce, które jeszcze nie działają
signal.addEventListener('abort', () => fetch(cleanupUrl))— nasłuchujące są wywoływane synchronicznie; żądanie fetch sprzątające po przerwaniu zostaje rozpoczęte, ale nie zakończy się podczas obsługi przerwania. Zaplanuj je przez otaczającą jeawait.
setTimeout(() => ctrl.abort(), N) działa (zestaw timerów jest dostępny i objęty testami WPT), ale AbortSignal.timeout(N) jest krótsze i lepiej wyraża zamiar, gdy potrzebujesz tylko sygnału przerwania po upływie terminu.
Zobacz także
- Transport fetch — pełny interfejs żądania, odpowiedzi i sygnału dla własnych transportów.
- Interfejsy API sieci (WPT) — odsetek pomyślnych testów WPT dla AbortController/AbortSignal.