Publicado em 6 de março de 2025
Uma página fica lenta e não responde quando tarefas longas mantêm a linha de execução principal ocupada, impedindo que ela faça outros trabalhos importantes, como responder à entrada do usuário. Como resultado, até mesmo controles de formulário integrados podem parecer corrompidos para os usuários, como se a página estivesse congelada, sem falar de componentes personalizados mais complexos.
scheduler.yield()
é uma forma de renderizar a linha de execução principal, permitindo que o navegador execute qualquer trabalho pendente de alta prioridade e continue a execução de onde parou. Isso mantém a página mais responsiva e, por sua vez, ajuda a melhorar a Interação com a próxima pintura (INP).
O scheduler.yield
oferece uma API ergonômica que faz exatamente o que diz: a execução da função chamada é pausada na expressão await scheduler.yield()
e retorna à linha de execução principal, dividindo a tarefa. A execução do restante da função, chamada de continuação da função, será programada para ser executada em uma nova tarefa de loop de eventos.
async function respondToUserClick() {
giveImmediateFeedback();
await scheduler.yield(); // Yield to the main thread.
slowerComputation();
}
O benefício específico de scheduler.yield
é que a continuação após o rendimento é programada para ser executada antes de executar qualquer outra tarefa semelhante que foi enfileirada pela página. Ele prioriza a continuação de uma tarefa em vez de iniciar novas.
Funções como setTimeout
ou scheduler.postTask
também podem ser usadas para dividir tarefas, mas essas continuações geralmente são executadas após qualquer nova tarefa já na fila, o que pode gerar atrasos longos entre a rendição para a linha de execução principal e a conclusão do trabalho.
Continuações priorizadas após a cessão
scheduler.yield
faz parte da API Prioritized Task Scheduling. Como desenvolvedores da Web, normalmente não falamos sobre a ordem em que o loop de eventos executa tarefas em termos de prioridades explícitas, mas as prioridades relativas sempre estão presentes, como um callback requestIdleCallback
executado após todos os callbacks setTimeout
em fila ou um listener de evento de entrada acionado, geralmente executado antes de uma tarefa em fila com setTimeout(callback, 0)
.
A programação de tarefas priorizadas torna isso mais explícito, facilitando a descoberta de qual tarefa será executada antes de outra, além de permitir o ajuste de prioridades para mudar essa ordem de execução, se necessário.
Como mencionado, a execução contínua de uma função após o rendimento com scheduler.yield()
recebe uma prioridade maior do que o início de outras tarefas. O conceito orientador é que a continuação de uma tarefa precisa ser executada primeiro, antes de passar para outras tarefas. Se a tarefa for um código bem comportado que é processado periodicamente para que o navegador possa fazer outras coisas importantes (como responder à entrada do usuário), ela não deve ser punida por ser processada, recebendo prioridade após outras tarefas semelhantes.
Confira um exemplo: duas funções em fila para serem executadas em tarefas diferentes usando setTimeout
.
setTimeout(myJob);
setTimeout(someoneElsesJob);
Nesse caso, as duas chamadas setTimeout
estão ao lado uma da outra, mas em uma página real, elas podem ser chamadas em lugares completamente diferentes, como um script próprio e um de terceiros configurando o trabalho de forma independente ou duas tarefas de componentes separados sendo acionadas no agendador do framework.
Confira como esse trabalho pode ficar no DevTools:
myJob
é sinalizado como uma tarefa longa, impedindo que o navegador faça qualquer outra coisa enquanto ele está em execução. Supondo que seja de um script próprio, podemos dividi-lo:
function myJob() {
// Run part 1.
myJobPart1();
// Yield with setTimeout() to break up long task, then run part2.
setTimeout(myJobPart2, 0);
}
Como myJobPart2
foi programado para ser executado com setTimeout
em myJob
, mas essa programação é executada depois que someoneElsesJob
já foi programado, confira como será a execução:
Dividimos a tarefa com setTimeout
para que o navegador possa ser responsivo durante o meio de myJob
, mas agora a segunda parte de myJob
só é executada depois que someoneElsesJob
é concluída.
Em alguns casos, isso pode ser aceitável, mas geralmente não é a melhor opção. myJob
estava cedendo à linha de execução principal para garantir que a página pudesse continuar respondendo à entrada do usuário, não para desistir totalmente da linha de execução principal. Nos casos em que o someoneElsesJob
é muito lento ou muitos outros jobs além do someoneElsesJob
também foram programados, pode levar muito tempo até que a segunda metade do myJob
seja executada. Provavelmente, essa não era a intenção do desenvolvedor quando ele adicionou setTimeout
a myJob
.
Digite scheduler.yield()
, que coloca a continuação de qualquer função que o invoca em uma fila de prioridade um pouco maior do que a inicialização de outras tarefas semelhantes. Se o myJob
for alterado para usá-lo:
async function myJob() {
// Run part 1.
myJobPart1();
// Yield with scheduler.yield() to break up long task, then run part2.
await scheduler.yield();
myJobPart2();
}
Agora a execução fica assim:
O navegador ainda tem a oportunidade de ser responsivo, mas agora a continuação da tarefa myJob
tem prioridade em relação ao início da nova tarefa someoneElsesJob
. Portanto, myJob
é concluído antes do início de someoneElsesJob
. Isso está muito mais próximo da expectativa de renderização para a linha de execução principal para manter a capacidade de resposta, sem desistir totalmente da linha de execução principal.
Herança de prioridade
Como parte da API de programação de tarefas priorizadas, scheduler.yield()
se combina bem com as prioridades explícitas disponíveis em scheduler.postTask()
. Sem uma prioridade definida explicitamente, um scheduler.yield()
em um callback scheduler.postTask()
vai funcionar basicamente como o exemplo anterior.
No entanto, se uma prioridade for definida, como usar uma prioridade 'background'
baixa:
async function lowPriorityJob() {
part1();
await scheduler.yield();
part2();
}
scheduler.postTask(lowPriorityJob, {priority: 'background'});
A continuação será programada com uma prioridade maior do que outras tarefas 'background'
, recebendo a continuação priorizada esperada antes de qualquer trabalho 'background'
pendente, mas ainda com uma prioridade menor do que outras tarefas padrão ou de alta prioridade. Ela continua sendo um trabalho 'background'
.
Isso significa que, se você programar um trabalho de baixa prioridade com um scheduler.postTask()
'background'
(ou com requestIdleCallback
), a continuação após um scheduler.yield()
também vai aguardar até que a maioria das outras tarefas seja concluída e a linha de execução principal esteja ociosa para ser executada, o que é exatamente o que você quer de rendimento em um job de baixa prioridade.
Como usar a API
Por enquanto, scheduler.yield()
só está disponível em navegadores baseados no Chromium. Para usá-lo, você precisa detectar o recurso e usar uma maneira secundária de desistência para outros navegadores.
scheduler-polyfill
é um pequeno polyfill para scheduler.postTask
e scheduler.yield
que usa internamente uma combinação de métodos para emular grande parte da capacidade das APIs de programação em outros navegadores, embora a herança de prioridade scheduler.yield()
não seja compatível.
Para quem quer evitar um polyfill, um método é renderizar usando setTimeout()
e aceitar a perda de uma continuação priorizada ou até mesmo não renderizar em navegadores sem suporte, se isso não for aceitável. Consulte a documentação do scheduler.yield()
em "Otimizar tarefas longas" para saber mais.
Os tipos wicg-task-scheduling
também podem ser usados para receber verificação de tipo e suporte ao ambiente de desenvolvimento integrado se você estiver detectando o recurso scheduler.yield()
e adicionando um substituto.
Saiba mais
Para mais informações sobre a API e como ela interage com as prioridades de tarefas e scheduler.postTask()
, consulte os documentos scheduler.yield()
e Programação de tarefas priorizadas no MDN.
Para saber mais sobre tarefas longas, como elas afetam a experiência do usuário e o que fazer com elas, leia sobre como otimizar tarefas longas.