Back

Tarefas em Segundo Plano no Navegador com a Scheduler API

Tarefas em Segundo Plano no Navegador com a Scheduler API

Sua aplicação parece rápida até que deixa de parecer. Um usuário clica num botão e nada acontece durante 300 milissegundos porque a thread principal está ocupada processando algo que não precisava acontecer naquele momento. Esse é o problema central que a Prioritized Task Scheduling API — comumente chamada de Scheduler API — foi projetada para resolver.

Este artigo aborda como scheduler.postTask() e scheduler.yield() oferecem controle prático sobre o agendamento da thread principal, quando usá-los em vez de abordagens mais antigas e qual é o panorama atual de suporte nos navegadores.

Principais Conclusões

  • A Scheduler API oferece controle granular sobre quando e como o trabalho da thread principal é executado, com três níveis de prioridade: user-blocking, user-visible e background.
  • scheduler.postTask() adia trabalho com prioridade explícita, enquanto scheduler.yield() divide tarefas longas para que o navegador possa lidar com a entrada do usuário entre as etapas.
  • Nenhum dos métodos move o trabalho para fora da thread principal. Para paralelismo real, use Web Workers.
  • Navegadores baseados em Chromium e o Firefox oferecem suporte à API; o Safari não, portanto sempre inclua um fallback com setTimeout.
  • Um agendamento mais inteligente pode melhorar o Interaction to Next Paint (INP) e a responsividade geral.

Por Que o Agendamento da Thread Principal Importa

O JavaScript é executado em uma única thread. Cada execução de script, atualização do DOM e manipulador de eventos disputa o mesmo recurso. Quando uma tarefa longa bloqueia essa thread, o navegador não consegue responder à entrada do usuário — e isso prejudica diretamente sua pontuação de Interaction to Next Paint (INP).

As soluções tradicionais têm limitações:

  • setTimeout(fn, 0) adia o trabalho, mas não oferece controle sobre prioridade. Ele é executado independentemente de quão ocupada a thread esteja.
  • requestIdleCallback() aguarda tempo ocioso, o que é útil para trabalho de baixa prioridade, mas não possui um sistema de prioridades, o suporte no Safari é limitado e pode atrasar indefinidamente sob carga.

A Scheduler API resolve ambos os problemas.

Como scheduler.postTask() Funciona

scheduler.postTask() é o ponto de entrada principal da Prioritized Task Scheduling API. Ele agenda um callback para ser executado na thread principal em um nível de prioridade especificado.

scheduler.postTask(() => {
  sendAnalyticsEvent('page_view');
}, { priority: 'background' });

Existem três níveis de prioridade:

  • user-blocking — prioridade mais alta, para trabalho que bloqueia diretamente uma interação do usuário
  • user-visible — o padrão, para trabalho que afeta o que o usuário vê, mas não é bloqueante
  • background — prioridade mais baixa, para análises, prefetching ou limpeza

Essa é a principal diferença em relação às APIs mais antigas: você não está apenas adiando o trabalho, está dizendo ao navegador quão importante ele é em relação a tudo o mais na fila.

scheduler.postTask() retorna uma Promise, o que facilita seu uso com async/await e o tratamento de erros de forma limpa:

try {
  await scheduler.postTask(() => processLargeDataset(data), {
    priority: 'background'
  });
} catch (err) {
  // Task was aborted or failed
  console.warn('Task did not complete:', err);
}

Dividindo Tarefas Longas com scheduler.yield()

scheduler.yield() é uma adição mais recente que resolve um problema diferente: o que fazer quando você já está dentro de uma tarefa longa e precisa dar ao navegador uma chance de lidar com a entrada antes de continuar?

async function processItems(items) {
  for (const item of items) {
    processItem(item);
    await scheduler.yield(); // yield back to the browser between items
  }
}

Cada await scheduler.yield() cria um ponto de verificação no qual o navegador pode lidar com interações pendentes do usuário antes de retomar seu loop. Por padrão, a continuação após yield() é agendada com prioridade user-visible, embora possa herdar a prioridade de uma chamada postTask() envolvente. Esta é uma das ferramentas mais práticas para reduzir tarefas longas sem reestruturar toda a sua base de código.

Um Esclarecimento Importante

Nem scheduler.postTask() nem scheduler.yield() movem o trabalho para fora da thread principal. As tarefas agendadas dessa forma ainda são executadas na thread principal — elas são apenas enfileiradas e priorizadas de forma mais inteligente. Se você precisa de execução paralela real, é para isso que existem os Web Workers.

Suporte dos Navegadores

O suporte à Scheduler API é sólido em navegadores baseados em Chromium (Chrome, Edge, Opera). O suporte no Firefox chegou muito depois do suporte no Chromium, e o Safari ainda não oferece suporte à API. Você pode verificar a matriz de suporte atual no Can I Use.

Uma verificação mínima de recurso antes de usá-la:

if ('scheduler' in window && 'postTask' in scheduler) {
  scheduler.postTask(myTask, { priority: 'background' });
} else {
  // Fallback for Safari and older browsers
  setTimeout(myTask, 0);
}

Para scheduler.yield() especificamente, verifique o suporte separadamente antes de confiar nele em produção, já que foi lançado depois do postTask() e tem cobertura mais limitada:

async function safeYield() {
  if ('scheduler' in window && 'yield' in scheduler) {
    await scheduler.yield();
  } else {
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

Quando Recorrer à Scheduler API

Use scheduler.postTask() quando precisar adiar trabalho não crítico — análises, prefetching ou processamento pós-renderização — com controle explícito de prioridade. Use scheduler.yield() quando tiver um loop ou processo de múltiplas etapas que corre o risco de bloquear o tratamento de entradas.

Se seus usuários estão principalmente no Chromium ou Firefox, a Scheduler API é prática para uso hoje, com detecção de recursos e fallbacks implementados. Para uma cobertura mais ampla, mantenha o fallback com setTimeout até que o Safari acompanhe.

Conclusão

A Scheduler API preenche uma lacuna antiga na forma como a plataforma web lida com o trabalho da thread principal. Em vez de depender de ferramentas rudimentares como setTimeout(fn, 0) ou do requestIdleCallback() com suporte inconsistente, agora você pode expressar intenção: esta tarefa é crítica, aquela pode esperar e este loop deve pausar para entrada do usuário. O resultado pode ser interações mais suaves e melhores pontuações de INP com relativamente pouca alteração de código. Combine-a com uma verificação de recurso e um fallback sensato, e você poderá adotá-la com segurança hoje.

Perguntas Frequentes

Não. Todas as tarefas agendadas com scheduler.postTask() ainda são executadas na thread principal. A API apenas altera quando e em que ordem as tarefas são executadas, atribuindo prioridades a elas. Se você precisa de paralelismo real para trabalho intensivo em CPU, use Web Workers, que executam código em uma thread separada e se comunicam com a thread principal por meio de mensagens.

Use scheduler.yield() dentro de uma função ou loop existente quando quiser pausar brevemente para que o navegador possa lidar com a entrada do usuário e, em seguida, retomar. Use scheduler.postTask() quando quiser agendar uma unidade discreta de trabalho para ser executada mais tarde com uma prioridade específica. Eles resolvem problemas relacionados, mas distintos: ceder no meio de uma tarefa versus enfileirar novas tarefas.

O INP mede a rapidez com que sua página responde às interações do usuário. Tarefas longas na thread principal são a causa mais comum de INP ruim. Ao ceder entre as etapas e atribuir menor prioridade ao trabalho não crítico, a Scheduler API ajuda a manter a thread principal disponível para lidar prontamente com cliques, toques e pressionamentos de tecla, o que pode reduzir a latência de interação.

Sim, desde que você inclua uma verificação de recurso e um fallback. Um padrão simples é testar a existência de scheduler.postTask e recorrer a setTimeout quando não estiver disponível. Isso mantém seu código funcionando no Safari e em navegadores mais antigos, ao mesmo tempo em que permite que os usuários do Chromium e Firefox se beneficiem do agendamento priorizado. Polyfills existem, mas geralmente são desnecessários para uso básico.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay