Ambas as duas respostas mais bem avaliadas estão erradas. Confira a descrição do MDN no modelo de simultaneidade e no loop de eventos , e deve ficar claro o que está acontecendo (esse recurso MDN é uma verdadeira jóia). E simplesmente usar setTimeout
pode adicionar problemas inesperados ao seu código, além de "resolver" esse pequeno problema.
O que realmente está acontecendo aqui não é que "o navegador ainda não esteja totalmente pronto porque é simultâneo" ou algo baseado em "cada linha é um evento que é adicionado à parte de trás da fila".
O jsfiddle fornecido pelo DVK ilustra de fato um problema, mas sua explicação para ele não está correta.
O que está acontecendo em seu código é que ele primeiro anexa um manipulador de click
eventos ao evento no #do
botão.
Em seguida, quando você realmente clica no botão, message
é criado um referenciando a função de manipulador de eventos, que é adicionada ao message queue
. Quando event loop
chega a essa mensagem, ele cria um frame
na pilha, com a chamada de função para o manipulador de eventos click no jsfiddle.
E é aqui que fica interessante. Estamos tão acostumados a pensar no Javascript como assíncrono que estamos propensos a ignorar esse pequeno fato: qualquer quadro deve ser executado, na íntegra, antes que o próximo quadro possa ser executado . Sem concorrência, pessoal.
O que isto significa? Isso significa que sempre que uma função é chamada da fila de mensagens, ela bloqueia a fila até que a pilha gerada seja esvaziada. Ou, em termos mais gerais, ele bloqueia até que a função retorne. E bloqueia tudo , incluindo operações de renderização DOM, rolagem e outros enfeites. Se você deseja confirmação, tente aumentar a duração da operação de longa duração no violino (por exemplo, execute o loop externo mais 10 vezes) e você notará que, enquanto ele é executado, não é possível rolar a página. Se durar o suficiente, seu navegador perguntará se você deseja interromper o processo, porque está deixando a página sem resposta. O quadro está sendo executado, e o loop de eventos e a fila de mensagens ficam bloqueados até terminar.
Então, por que esse efeito colateral do texto não está sendo atualizado? Porque enquanto você ter alterado o valor do elemento no DOM - você pode console.log()
seu valor imediatamente depois de mudar-lo e ver que ele tenha sido alterado (que mostra por que a explicação de DVK não é correto) - o navegador está aguardando a pilha para esgotam (a on
função do manipulador para retornar) e, portanto, a mensagem para concluir, para que possa eventualmente executar a mensagem que foi adicionada pelo tempo de execução como uma reação à nossa operação de mutação e para refletir essa mutação na interface do usuário .
Isso ocorre porque, na verdade, estamos aguardando o código concluir a execução. Não dissemos "alguém busca isso e depois chama essa função com os resultados, obrigado, e agora estou pronto para retornar, faça o que for agora", como costumamos fazer com nosso Javascript assíncrono baseado em eventos. Entramos numa função de clique manipulador de eventos, nós atualizamos um elemento DOM, chamamos outra função, as outras obras de função por um longo tempo e, em seguida, retorna, nós, em seguida, atualizar o mesmo elemento DOM, e , em seguida, voltamos a partir da função inicial, efetivamente esvaziamento a pilha. E , em seguida, o navegador pode chegar ao próximo mensagem na fila, que pode muito bem ser uma mensagem gerada por nós, desencadeando algumas interna "on-DOM-mutação" tipo de evento.
A interface do usuário do navegador não pode (ou escolhe não) atualizar a interface do usuário até que o quadro atualmente em execução seja concluído (a função retornou). Pessoalmente, acho que isso é mais por design do que por restrição.
Por que a setTimeout
coisa funciona então? Isso é feito porque remove efetivamente a chamada para a função de longa execução de seu próprio quadro, agendando para que seja executada posteriormente no window
contexto, para que ele possa retornar imediatamente e permitir que a fila de mensagens processe outras mensagens. E a ideia é que a mensagem "na atualização" da interface do usuário que foi acionada por nós em Javascript ao alterar o texto no DOM agora esteja à frente da mensagem na fila para a função de longa duração, para que a atualização da interface do usuário aconteça antes de bloquearmos por muito tempo.
Observe que a) a função de execução demorada ainda bloqueia tudo quando é executada eb) você não tem garantia de que a atualização da interface do usuário esteja realmente à frente na fila de mensagens. No meu navegador Chrome de junho de 2018, um valor de 0
"não corrige" o problema que o violino demonstra - 10 sim. Na verdade, sou um pouco sufocado por isso, porque me parece lógico que a mensagem de atualização da interface do usuário seja colocada na fila antes dela, pois seu gatilho é executado antes de agendar a função de longa execução para ser executada "mais tarde". Mas talvez haja algumas otimizações no mecanismo V8 que possam interferir, ou talvez meu entendimento esteja faltando.
Ok, então qual é o problema do uso setTimeout
e qual a melhor solução para esse caso em particular?
Primeiro, o problema de usar setTimeout
qualquer manipulador de eventos como esse, para tentar aliviar outro problema, é propenso a mexer com outro código. Aqui está um exemplo da vida real do meu trabalho:
Um colega, em um entendimento mal informado sobre o loop de eventos, tentou "encadear" o Javascript usando algum código de renderização de modelo usado setTimeout 0
para sua renderização. Ele não está mais aqui para perguntar, mas posso presumir que talvez ele tenha inserido temporizadores para medir a velocidade de renderização (que seria o retorno imediato das funções) e descobriu que usar essa abordagem resultaria em respostas incrivelmente rápidas dessa função.
O primeiro problema é óbvio; você não pode encadear o javascript, então você não ganha nada aqui enquanto adiciona ofuscação. Em segundo lugar, agora você separou efetivamente a renderização de um modelo da pilha de possíveis ouvintes de eventos que podem esperar que esse mesmo modelo tenha sido renderizado, enquanto pode muito bem não ter sido. O comportamento real dessa função agora não era determinista, como era - sem o saber - qualquer função que a executasse ou dependesse dela. Você pode fazer palpites, mas não pode codificar adequadamente seu comportamento.
A "correção" ao escrever um novo manipulador de eventos que dependia de sua lógica também era usar setTimeout 0
. Mas isso não é uma correção, é difícil de entender e não é divertido depurar erros causados por códigos como este. Às vezes, não há problema algum, outras vezes, falha de forma consistente e, novamente, às vezes funciona e quebra esporadicamente, dependendo do desempenho atual da plataforma e do que quer que aconteça no momento. É por isso que eu pessoalmente aconselho a não usar esse hack ( é um hack, e todos devemos saber que é), a menos que você realmente saiba o que está fazendo e quais são as consequências.
Mas o que pode nós fazer em vez disso? Bem, como o artigo mencionado no MDN sugere, divida o trabalho em várias mensagens (se você puder) para que outras mensagens enfileiradas possam ser intercaladas com o seu trabalho e executadas durante a execução ou use um trabalhador da Web, que pode executar em conjunto com sua página e retorne resultados quando terminar com seus cálculos.
Ah, e se você está pensando: "Bem, eu não poderia simplesmente colocar um retorno de chamada na função de longa duração para torná-lo assíncrono?", Então não. O retorno de chamada não o torna assíncrono, ainda será necessário executar o código de longa duração antes de chamar explicitamente o retorno de chamada.