Padrões do AbortController
AbortController e AbortSignal são implementações WHATWG completas em Phasis. Os auxiliares padrão de composição (AbortSignal.any, AbortSignal.timeout) também estão incluídos. Esta página apresenta os padrões mais úteis e explica como o cancelamento se propaga pela fronteira PHP.
Cancelar uma solicitação fetch
const ctrl = new AbortController();
const p = fetch(url, { signal: ctrl.signal });
ctrl.abort(); // p rejects with AbortError DOMException
O motivo padrão é AbortError, mas você pode fornecer o seu próprio:
ctrl.abort(new Error('user cancelled'));
fetch rejeita com o valor que você passou.
Timeout
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
AbortSignal.timeout(ms) retorna um sinal que é abortado após ms. O timer é acionado pelo loop de eventos do mecanismo, o mesmo que controla setTimeout / queueMicrotask (consulte API: loop de eventos). O transporte fetch verifica o sinal durante a transferência — no transporte padrão CurlTransport, por meio de CURLOPT_PROGRESSFUNCTION —, portanto a solicitação é abortada assim que o prazo é atingido.
Por padrão, o loop usa tempo virtual: adianta o relógio até o próximo prazo pendente em vez de dormir PHP. Assim, um fetch(url, { signal: AbortSignal.timeout(5000) }) termina antes do prazo ou é abortado no próximo ciclo do loop — nunca faz PHP esperar. Para usar tempo real, o host precisa executar tickEventLoop() conforme o relógio de parede.
Corrida: o primeiro cancelamento vence
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
Propagação entre camadas
Envolva seu próprio trabalho assíncrono em um loop que verifica o sinal:
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;
}
O fetch herda o sinal diretamente; o if (signal.aborted) manual cobre os intervalos entre solicitações em que o loop da página continuaria em execução.
Como ouvir o evento de aborto
ctrl.signal.addEventListener('abort', () => {
releaseDatabaseLock();
cancelDownstreamWork();
});
signal.reason é definido antes de os listeners serem acionados.
Sinal em um transporte fetch personalizado
Se você substituir o transporte por meio de setFetchTransport(), o sinal será passado à sua função chamável como JsObject. Respeite-o verificando-o periodicamente:
$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',
);
}
// ...
});
Lançar TransportException com kind: 'aborted' faz a JS do lado fetch rejeitar com AbortError DOMException — como se o loop de aborto da especificação tivesse sido executado.
Para backends com cancelamento nativo (Guzzle's on_progress, Symfony's ResponseInterface::cancel), use a mesma estrutura de verificação e lançamento.
Padrões que ainda não funcionarão
signal.addEventListener('abort', () => fetch(cleanupUrl))— os listeners são acionados de forma síncrona; a solicitação fetch de limpeza começa, mas não termina durante o despacho do aborto. Agende-a peloawaitao redor.
setTimeout(() => ctrl.abort(), N) funciona (a família de timers está incluída e tem cobertura WPT), mas AbortSignal.timeout(N) é mais curto e expressa melhor a intenção quando você só quer um sinal de aborto após um prazo.
Veja também
- Transporte fetch — interface completa de solicitação/resposta/sinal para transportes personalizados.
- APIs da Web (WPT) — taxa de aprovação nos testes WPT de AbortController/AbortSignal.