W3docs

JavaScript Debounce e Throttle

Aprenda a limitar a frequência de funções em JavaScript com debounce e throttle — o que cada um faz, como implementá-los e quando usar cada um.

Alguns eventos disparam com muito mais frequência do que você consegue responder de forma útil. Digitar em uma caixa de pesquisa aciona um evento input a cada tecla pressionada; rolar uma página pode emitir centenas de eventos scroll por segundo; resize e mousemove são igualmente verbosos. Se cada evento executa um trabalho custoso — uma requisição de rede, um cálculo de layout, uma re-renderização — seu aplicativo trava. Debounce e throttle são dois pequenos wrappers que limitam a frequência com que uma função é executada, mantendo a responsividade alta sem alterar o que a função faz.

Ambos são padrões clássicos de decorator: recebem uma função e retornam uma nova função com o mesmo comportamento mais uma regra de limitação de taxa. São construídos sobre closures para manter estado entre chamadas e sobre temporizadores como setTimeout para adiar ou controlar a execução.

A Ideia Central

As duas técnicas respondem à mesma pergunta — "com que frequência isso deve ser executado?" — de formas opostas:

  • Debounce aguarda uma pausa. Ele adia a chamada até que N milissegundos tenham passado desde a última invocação. Se as chamadas continuam chegando, o temporizador continua sendo redefinido e a função nunca é executada. Pense em: "aguardar o silêncio."
  • Throttle impõe um ritmo constante. Ele permite que a função seja executada no máximo uma vez a cada N milissegundos, não importa quantas vezes seja chamada no intervalo. Pense em: "batimento cardíaco regular."
AspectoDebounceThrottle
Dispara quandoA atividade para por N msNo máximo uma vez a cada N ms
Durante uma rajadaNada é executado até a rajada terminarExecuta em um intervalo fixo
Modelo mental"Aguardar o silêncio""Ritmo constante"
Bom paraPesquisa ao digitar, salvamento automático, resize finalizadoRastreamento de scroll, drag, scroll infinito

Debounce

Uma função com debounce cancela qualquer temporizador pendente a cada chamada e agenda um novo. Somente quando as chamadas param por delay milissegundos a função encapsulada de fato é executada.

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

Dois detalhes tornam isso robusto. O wrapper coleta cada argumento com parâmetros rest (...args) e os encaminha, de modo que a função encapsulada receba exatamente o que o chamador passou. E invoca fn com fn.apply(this, args) para que o this original seja preservado — importante quando a função com debounce é um método em um objeto. (Veja call e apply e vinculação de função para entender por que encaminhar this importa.)

Veja em ação. Chamar a função encapsulada repetidamente aciona apenas uma execução real, após a atividade se estabilizar:

javascript— editable

Como cada tecla redefine o relógio, debounce é ideal quando você quer reagir após o usuário terminar: pesquisa ao digitar, salvamento automático de um rascunho, validação de um campo quando a digitação para, ou recálculo de layout somente quando um redimensionamento de janela se estabilizou.

Throttle

Uma função com throttle é executada imediatamente e depois ignora chamadas adicionais até que um período de espera termine. Isso garante uma frequência máxima em vez de aguardar uma pausa.

function throttle(fn, limit) {
  let inThrottle = false;
  return function (...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => (inThrottle = false), limit);
    }
  };
}

O flag inThrottle, mantido na closure, atua como um portão. A primeira chamada passa e o portão fecha; qualquer chamada durante o período de espera é descartada; quando o temporizador dispara, o portão reabre para a próxima chamada.

javascript— editable

Throttle é adequado para qualquer coisa que flua continuamente e onde você queira atualizações regulares em vez de cada uma individualmente: rastrear posição de scroll, lidar com mousemove durante um drag, carregar mais conteúdo em scroll infinito, ou limitar a frequência com que você acessa uma API com limite de taxa.

Borda Inicial vs. Borda Final

Há uma escolha de design sutil em ambos os wrappers: a função deve disparar na borda inicial (a primeira chamada, imediatamente) ou na borda final (após o delay/período de espera)?

  • O debounce acima é de borda final: nada acontece até que a atividade pare. Um debounce de borda inicial executaria na primeira chamada e ignoraria o restante.
  • O throttle acima é de borda inicial: dispara imediatamente e depois controla. Um throttle de borda final também executaria mais uma vez no final da janela para capturar o valor final.

Esses comportamentos de borda importam na prática — um throttle de borda final no scroll, por exemplo, garante que você não perca a posição final de scroll quando o usuário para.

Informação

Para código em produção, prefira uma implementação testada em batalha como _.debounce e _.throttle do lodash. Elas lidam com bordas iniciais e finais, uma API cancel()/flush(), e uma opção maxWait (para que uma função com debounce ainda seja executada eventualmente durante atividade contínua). Entender as versões básicas acima é essencial, mas raramente você precisa criar a sua própria.

Um Exemplo Real com DOM

Conectar debounce a um campo de pesquisa é o caso de uso canônico. Adicionamos um listener (veja manipulação de eventos no DOM) e deixamos o wrapper decidir quando o trabalho realmente é executado:

const input = document.querySelector('#search');

function search(event) {
  console.log('Querying API for:', event.target.value);
  // fetch(`/api/search?q=${event.target.value}`) ...
}

const debouncedSearch = debounce(search, 400);

input.addEventListener('input', debouncedSearch);

Agora a requisição de rede dispara somente quando o usuário pausa por 400 ms, em vez de a cada tecla pressionada — uma caixa de pesquisa que antes disparava uma dúzia de requisições para hello agora dispara uma. Note que o listener recebe o objeto event do DOM e, como nosso wrapper encaminha todos os argumentos, search ainda o recebe intacto.

Aviso

Temporizadores e listeners mantêm referências, por isso limpe-os quando não forem mais necessários. Em um aplicativo de página única ou componente, remova o listener ao desmontar (por exemplo, ao desmontar o componente) e cancele qualquer temporizador pendente para evitar vazamentos de memória e callbacks sendo acionados em elementos que não existem mais:

input.removeEventListener('input', debouncedSearch);

Um debounce de produção normalmente também expõe um método cancel() que chama clearTimeout por você.

Escolhendo Entre Eles

Quando você não tem certeza qual usar, pergunte-se o que importa para você:

  • Você se importa apenas com o estado final após uma rajada de atividade (o termo de pesquisa concluído, o tamanho da janela estabilizado)? Use debounce.
  • Você quer feedback contínuo e regular durante a atividade (progresso do scroll, posição de um elemento arrastado)? Use throttle.

Ambos são leves, independentes de framework, e se constroem diretamente sobre closures e temporizadores — as mesmas bases por trás das arrow functions capturando this e das ferramentas de agendamento que você já viu.

Teste Seu Conhecimento

Prática
Qual técnica dispara a função no máximo uma vez por intervalo de tempo fixo, não importa quantas vezes ela seja chamada?
Qual técnica dispara a função no máximo uma vez por intervalo de tempo fixo, não importa quantas vezes ela seja chamada?
Prática
Você quer enviar uma requisição de pesquisa somente após o usuário parar de digitar. Qual é a ferramenta certa?
Você quer enviar uma requisição de pesquisa somente após o usuário parar de digitar. Qual é a ferramenta certa?
Prática
Por que os exemplos de wrappers debounce e throttle chamam a função original com `fn.apply(this, args)` em vez de simplesmente `fn(args)`?
Por que os exemplos de wrappers debounce e throttle chamam a função original com `fn.apply(this, args)` em vez de simplesmente `fn(args)`?
Was this page helpful?