Combinar múltiplos motivos de cancelamento em uma única operação assíncrona sempre exigiu código manual: escutar vários sinais, propagar o cancelamento e limpar os listeners depois. O método estático AbortSignal.any() resolve isso recebendo uma lista de sinais e devolvendo um novo AbortSignal que é abortado assim que qualquer um deles for abortado.
O problema: combinar sinais manualmente
function combinarSinais(sinais) {
const controller = new AbortController();
for (const sinal of sinais) {
if (sinal.aborted) {
controller.abort(sinal.reason);
break;
}
sinal.addEventListener('abort', () => controller.abort(sinal.reason));
}
return controller.signal;
}
const sinalCombinado = combinarSinais([timeoutSignal, botaoCancelarSignal]);
Funciona, mas é fácil esquecer de remover os listeners depois que um dos sinais dispara, gerando vazamento de memória quando o combinador é criado repetidamente (por exemplo, a cada requisição).
Como funciona o AbortSignal.any()
O método recebe qualquer iterável de sinais e devolve um único AbortSignal combinado, sem precisar criar um AbortController manualmente:
const timeoutSignal = AbortSignal.timeout(5000);
const controllerCancelar = new AbortController();
const sinalCombinado = AbortSignal.any([
timeoutSignal,
controllerCancelar.signal,
]);
sinalCombinado.addEventListener('abort', () => {
console.log('Abortado! Motivo:', sinalCombinado.reason);
});
controllerCancelar.abort(new Error('Usuário cancelou'));
// Abortado! Motivo: Error: Usuário cancelou
O sinal combinado assume o reason do primeiro sinal de origem que for abortado, e a limpeza interna dos listeners é feita automaticamente pelo motor do JavaScript.
Exemplo prático: fetch com timeout e cancelamento manual
Um dos casos mais comuns é uma requisição que deve ser cancelada tanto por timeout quanto por ação do usuário (por exemplo, ao sair da página ou clicar em "cancelar"):
async function buscarDados(url, botaoCancelar) {
const controllerCancelar = new AbortController();
botaoCancelar.addEventListener('click', () => controllerCancelar.abort(), { once: true });
const sinal = AbortSignal.any([
AbortSignal.timeout(8000),
controllerCancelar.signal,
]);
const resposta = await fetch(url, { signal: sinal });
return resposta.json();
}
Se o timeout de 8 segundos vencer ou o usuário clicar no botão, o fetch é abortado com o motivo correspondente, sem que a função precise saber qual dos dois aconteceu para reagir.
Exemplo prático: cancelamento em cadeia
O sinal combinado também é útil para propagar cancelamento por várias camadas de uma aplicação, unindo um sinal recebido de fora com um controle interno:
function processarPedido(pedido, sinalExterno) {
const controllerInterno = new AbortController();
// Cancela automaticamente se o processamento demorar demais,
// além de respeitar um cancelamento vindo de fora.
const sinal = AbortSignal.any([sinalExterno, controllerInterno.signal]);
setTimeout(() => controllerInterno.abort(new Error('Processamento excedeu o limite interno')), 3000);
return executarEtapas(pedido, { signal: sinal });
}
Quando usar
- Combinar um timeout (
AbortSignal.timeout()) com um cancelamento manual acionado pelo usuário. - Unir um sinal recebido como parâmetro de uma função com um controle de cancelamento interno dessa mesma função.
- Cancelar uma operação assim que qualquer um entre vários eventos independentes acontecer (navegação, desmontagem de componente, erro em outra parte do fluxo).
- Substituir a combinação manual de múltiplos
AbortController, evitando vazamento de listeners.
Cuidados
O sinal retornado por AbortSignal.any() é somente leitura: não existe um AbortController associado a ele para abortá-lo diretamente. Se for necessário abortar também "de fora" por um motivo próprio, basta incluir o signal de um AbortController extra na lista passada para any().
Compatibilidade
O AbortSignal.any() tem suporte nos navegadores modernos desde março de 2024 e também está disponível no Node.js recente. Ainda assim, vale conferir antes de usar em produção se o projeto precisar suportar ambientes mais antigos.
Conclusão
AbortSignal.any() elimina a necessidade de escrever um combinador manual de sinais toda vez que uma operação assíncrona precisa responder a mais de um motivo de cancelamento, tornando o código de timeout e cancelamento mais curto e livre de vazamentos de listeners.
0 Comentário
Deixe seu Comentário!
Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.