AbortSignal.any(): Combinando Múltiplos Sinais de Cancelamento

Como o método estático AbortSignal.any() combina vários sinais de cancelamento em um único AbortSignal, eliminando o combinador manual de AbortController usado para unir timeout e cancelamento pelo usuário.

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

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.

caniuse

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.

Tecnologia:
js

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.