Quando o JavaScript é síncrono?

202

Fiquei com a impressão de que o JavaScript sempre foi assíncrono. No entanto, eu aprendi que há situações em que não existe (ou seja, manipulações DOM). Existe uma boa referência sobre quando será síncrono e quando será assíncrono? O jQuery afeta isso?

Brian
fonte
14
Sempre com exceção do ajax.
defau1t
A resposta aceita está errada e, enganosa, verifique-a.
Suraj Jain #
2
Também foi útil assistindo youtube.com/watch?v=8aGhZQkoFbQ para entender o ciclo de eventos, e como a pilha, APIs web, ea tarefa trabalho fila com relação ao síncrona e assíncrona
mtpultz
1
@ defau1t Isso não está errado, o JavaScript sempre é síncrono; quando a chamada ajax termina, o retorno de chamada termina na fila; como é uma exceção à natureza síncrona do script java.
Suraj Jain

Respostas:

281

O JavaScript é sempre síncrono e de thread único. Se você estiver executando um bloco de código JavaScript em uma página, nenhum outro JavaScript nessa página será atualmente executado.

JavaScript é assíncrono apenas no sentido em que pode fazer, por exemplo, chamadas de Ajax. A chamada do Ajax interromperá a execução e outro código poderá ser executado até que a chamada retorne (com êxito ou não), momento em que o retorno de chamada será executado de forma síncrona. Nenhum outro código estará em execução neste momento. Não interromperá nenhum outro código atualmente em execução.

Os temporizadores de JavaScript operam com esse mesmo tipo de retorno de chamada.

A descrição de JavaScript como assíncrono talvez seja enganosa. É mais preciso dizer que o JavaScript é síncrono e de thread único com vários mecanismos de retorno de chamada.

O jQuery tem uma opção nas chamadas do Ajax para torná-las sincronizadas (com a async: falseopção). Os iniciantes podem ficar tentados a usar isso incorretamente, pois permite um modelo de programação mais tradicional ao qual se pode estar mais acostumado. A razão pela qual é problemático é que essa opção bloqueia todo o JavaScript na página até sua conclusão, incluindo todos os manipuladores de eventos e cronômetros.

cleto
fonte
31
desculpe, eu não entendi direito esta afirmação "O código irá parar de executar até que a chamada retorne (com sucesso ou com erro)". você poderia elaborar. Como essa afirmação pode ser verdadeira quando você também diz "Não interromperá nenhum outro código em execução"; Você está falando de código de retorno de chamada apenas na primeira instrução? Por favor me esclareça.
precisa
2
Nettuts tem um tutorial que é muito bom em explicar os conceitos básicos de assíncrono aqui: net.tutsplus.com/tutorials/javascript-ajax/...
RobW
26
@cletus A instrução "O código para de executar até que a chamada retorne" precisa de correção, porque a execução não para. A execução do código pode continuar. Caso contrário, isso significaria que a chamada é síncrona.
HS.
1
Também não entendi essa afirmação.
towry
12
Essa resposta é incrivelmente enganosa e confusa. Por favor, veja a resposta do CMS ou Faraz Ahmad.
iono
214

O JavaScript é único e possui um modelo de execução síncrona. Encadeamento único significa que um comando está sendo executado por vez. Síncrono significa um de cada vez, ou seja, uma linha de código está sendo executada ao mesmo tempo para que o código apareça. Então, em JavaScript, uma coisa está acontecendo de cada vez.

Contexto de Execução

O mecanismo JavaScript interage com outros mecanismos no navegador. Na pilha de execução do JavaScript, há um contexto global na parte inferior e, quando invocamos funções, o mecanismo JavaScript cria novos contextos de execução para as respectivas funções. Quando a função chamada sai, seu contexto de execução é exibido na pilha e o próximo contexto de execução é exibido e assim por diante ...

Por exemplo

function abc()
{
   console.log('abc');
}


function xyz()
{
   abc()
   console.log('xyz');
}
var one = 1;
xyz();

No código acima, um contexto de execução global será criado e, nesse contexto var one, será armazenado e seu valor será 1 ... quando a chamada xyz () for chamada, um novo contexto de execução será criado e se definimos alguma variável na função xyz, essas variáveis ​​seriam armazenadas no contexto de execução de xyz (). Na função xyz, invocamos abc () e, em seguida, o contexto de execução abc () é criado e colocado na pilha de execução ... Agora, quando abc () termina, seu contexto é retirado da pilha, o contexto xyz () é retirado de empilhar e, em seguida, o contexto global será exibido ...

Agora, sobre retornos de chamada assíncronos; assíncrono significa mais de um de cada vez.

Assim como a pilha de execução, há a fila de eventos . Quando queremos ser notificados sobre algum evento no mecanismo JavaScript, podemos ouvi-lo, e esse evento é colocado na fila. Por exemplo, um evento de solicitação do Ajax ou evento de solicitação de HTTP.

Sempre que a pilha de execução estiver vazia, como mostrado no exemplo de código acima, o mecanismo JavaScript verifica periodicamente a fila de eventos e verifica se há algum evento a ser notificado. Por exemplo, na fila, havia dois eventos, uma solicitação ajax e uma solicitação HTTP. Ele também procura ver se existe uma função que precise ser executada nesse gatilho de evento ... Portanto, o mecanismo JavaScript é notificado sobre o evento e conhece a respectiva função a ser executada nesse evento ... Portanto, o mecanismo JavaScript chama o função manipulador, no caso de exemplo, por exemplo, AjaxHandler () será invocado e, como sempre, quando uma função é invocada, seu contexto de execução é colocado no contexto de execução e agora a execução da função termina e a solicitação de evento ajax também é removida da fila de eventos ... Quando AjaxHandler () termina, a pilha de execução está vazia, portanto, o mecanismo novamente analisa a fila de eventos e executa a função de manipulador de eventos da solicitação HTTP, que foi a próxima na fila. É importante lembrar que a fila de eventos é processada apenas quando a pilha de execução está vazia.

Por exemplo, consulte o código abaixo, explicando a pilha de execução e a manipulação da fila de eventos pelo mecanismo Javascript.

function waitfunction() {
    var a = 5000 + new Date().getTime();
    while (new Date() < a){}
    console.log('waitfunction() context will be popped after this line');
}

function clickHandler() {
    console.log('click event handler...');   
}

document.addEventListener('click', clickHandler);


waitfunction(); //a new context for this function is created and placed on the execution stack
console.log('global context will be popped after this line');

E

<html>
    <head>

    </head>
    <body>

        <script src="program.js"></script>
    </body>
</html>

Agora execute a página da web, clique na página e veja a saída no console. A saída será

waitfunction() context will be popped after this line
global context will be emptied after this line
click event handler...

O mecanismo JavaScript está executando o código de forma síncrona, conforme explicado na parte do contexto de execução, o navegador está colocando as coisas de forma assíncrona na fila de eventos. Portanto, as funções que levam muito tempo para serem concluídas podem interromper a manipulação de eventos. Coisas que acontecem em um navegador, como eventos, são tratadas dessa maneira por JavaScript; se houver um ouvinte em execução, o mecanismo executará quando a pilha de execução estiver vazia. Como os eventos são processados ​​na ordem em que ocorrem, a parte assíncrona é sobre o que está acontecendo fora do mecanismo, ou seja, o que o mecanismo deve fazer quando esses eventos externos acontecerem.

Portanto, o JavaScript é sempre síncrono.


fonte
16
Esta resposta é muito clara, deve receber mais votos.
ranu
7
Certamente a melhor explicação para o comportamento assíncrono do Javascript que li.
Charles Jaimet
1
Boa explicação do contexto de execução e fila.
Divyanshu Maithani
1
É claro que isso exige que você leia um pouco sobre a pilha de contexto de execução, e apenas a adição dela como vazia e que event me faz finalmente sentir que entendo determinamente a execução de scripts java. O pior é que sinto que é preciso apenas uma página de leitura, mas acho que quase não aparece em nenhum lugar. Então, por que ninguém apenas diz isso? Ou eles não sabem ou o quê? Mas acho que se um tutorial js tivesse isso, poderia ter me poupado muito tempo. >: |
marshal craft
2
Explicação perfeita!
Julsy
100

O JavaScript é de thread único e o tempo todo você trabalha em uma execução de fluxo de código síncrono normal.

Bons exemplos do comportamento assíncrono que o JavaScript pode ter são eventos (interação do usuário, resultados de solicitação do Ajax, etc.) e timers, basicamente ações que podem ocorrer a qualquer momento.

Eu recomendo que você dê uma olhada no seguinte artigo:

Esse artigo ajudará você a entender a natureza de thread único do JavaScript e como os cronômetros funcionam internamente e como funciona a execução assíncrona do JavaScript.

assíncrono

CMS
fonte
Resposta aceita enganosa, podemos fazer algo nesse caso? /
Suraj Jain
8

Para alguém que realmente entende como o JS funciona, essa pergunta pode parecer ruim, no entanto, a maioria das pessoas que usa o JS não tem um nível tão profundo de insight (e não necessariamente precisa dele) e, para eles, esse é um ponto bastante confuso. tente responder dessa perspectiva.

JS é síncrono na maneira como seu código é executado. cada linha é executada somente após a linha antes de ser concluída e se essa linha chama uma função após a conclusão ect ...

O principal ponto de confusão decorre do fato de que seu navegador é capaz de dizer ao JS para extrair mais código a qualquer momento (veja como você pode extrair mais código JS em uma página do console). Como exemplo, o JS tem funções de retorno de GETchamada cujo objetivo é permitir que o JS se comporte de forma assíncrona, para que outras partes do JS possam ser executadas enquanto aguarda uma função JS executada (ou seja, uma chamada) retornar uma resposta, o JS continuará em execução até o navegador tem uma resposta nesse ponto, o loop de eventos (navegador) executará o código JS que chama a função de retorno de chamada.

Como o loop de eventos (navegador) pode inserir mais JS para ser executado em qualquer ponto nesse sentido, o JS é assíncrono (as principais coisas que farão com que o navegador insira o código JS são tempos limite, retornos de chamada e eventos)

Espero que isso seja claro o suficiente para ajudar alguém.

Yehuda Schwartz
fonte
4

Definição

O termo "assíncrono" pode ser usado com significados ligeiramente diferentes, resultando em respostas aparentemente conflitantes aqui, enquanto na verdade não são. A Wikipedia sobre Assincronia tem esta definição:

A assincronia, na programação de computadores, refere-se à ocorrência de eventos independentes do fluxo principal do programa e às formas de lidar com esses eventos. Esses podem ser eventos "externos", como a chegada de sinais ou ações instigadas por um programa que ocorrem simultaneamente à execução do programa, sem que o programa seja bloqueado para aguardar resultados.

código não JavaScript pode enfileirar esses eventos "externos" para algumas das filas de eventos do JavaScript. Mas isso é o mais longe possível.

Sem Preempção

Não há interrupção externa da execução do código JavaScript para executar outro código JavaScript no seu script. Partes do JavaScript são executadas uma após a outra, e a ordem é determinada pela ordem dos eventos em cada fila de eventos e pela prioridade dessas filas.

Por exemplo, você pode ter certeza absoluta de que nenhum outro JavaScript (no mesmo script) será executado enquanto o seguinte trecho de código estiver em execução:

let a = [1, 4, 15, 7, 2];
let sum = 0;
for (let i = 0; i < a.length; i++) {
    sum += a[i];
}

Em outras palavras, não há preempção no JavaScript. O que quer que esteja nas filas de eventos, o processamento desses eventos terá que esperar até que esse trecho de código seja executado até a conclusão. A especificação EcmaScript diz na seção 8.4 Trabalhos e filas de trabalhos :

A execução de um trabalho pode ser iniciada apenas quando não houver contexto de execução em execução e a pilha de contexto de execução estiver vazia.

Exemplos de assincronia

Como outros já escreveram, há várias situações em que a assincronia entra em jogo no JavaScript, e sempre envolve uma fila de eventos, que só pode resultar na execução do JavaScript quando não há outro código JavaScript em execução:

  • setTimeout(): o agente (por exemplo, navegador) colocará um evento em uma fila de eventos quando o tempo limite expirar. O monitoramento da hora e a colocação do evento na fila acontecem por código não JavaScript, e você pode imaginar que isso acontece paralelamente à execução potencial de algum código JavaScript. Mas o retorno de chamada fornecido setTimeoutsomente pode ser executado quando o código JavaScript atualmente em execução foi executado e a fila de eventos apropriada está sendo lida.

  • fetch(): o agente usará funções do SO para executar uma solicitação HTTP e monitorar qualquer resposta recebida. Novamente, essa tarefa não JavaScript pode ser executada em paralelo com algum código JavaScript que ainda está em execução. Mas o procedimento de resolução da promessa, que resolverá a promessa retornada por fetch(), só poderá ser executado quando o JavaScript atualmente em execução for concluído.

  • requestAnimationFrame(): o mecanismo de renderização do navegador (não JavaScript) colocará um evento na fila JavaScript quando estiver pronto para executar uma operação de pintura. Quando o evento JavaScript é processado, a função de retorno de chamada é executada.

  • queueMicrotask(): coloca imediatamente um evento na fila de microtask. O retorno de chamada será executado quando a pilha de chamadas estiver vazia e esse evento for consumido.

Existem muitos outros exemplos, mas todas essas funções são fornecidas pelo ambiente host, não pelo EcmaScript principal. Com o EcmaScript básico, você pode colocar de forma síncrona um evento em uma fila de tarefas do Promise Promise.resolve().

Construções de linguagem

EcmaScript fornece várias construções de linguagem para suportar o padrão de assincronia, tais como yield, async, await. Mas não se engane: nenhum código JavaScript será interrompido por um evento externo. A "interrupção" que yielde awaitparecem fornecer é apenas uma maneira controlada, predefinido de retornar de uma chamada de função e restaurar seu contexto de execução, mais tarde, seja por código JS (no caso de yield), ou a fila de eventos (no caso de await)

Tratamento de eventos DOM

Quando o código JavaScript acessa a API do DOM, isso pode, em alguns casos, fazer com que a API do DOM acione uma ou mais notificações síncronas. E se o seu código tiver um manipulador de eventos ouvindo isso, ele será chamado.

Isso pode parecer uma simultaneidade preventiva, mas não é: assim que os manipuladores de eventos retornarem, a API DOM também retornará e o código JavaScript original continuará.

Em outros casos, a API do DOM enviará apenas um evento na fila de eventos apropriada e o JavaScript o buscará assim que a pilha de chamadas for esvaziada.

Veja eventos síncronos e assíncronos

trincot
fonte
0

É síncrono em todos os casos.

Exemplo de thread de bloqueio com Promises:

  const test = () => new Promise((result, reject) => {
    const time = new Date().getTime() + (3 * 1000);

    console.info('Test start...');

    while (new Date().getTime() < time) {
      // Waiting...
    }

    console.info('Test finish...');
  });

  test()
    .then(() => console.info('Then'))
    .finally(() => console.info('Finally'));

  console.info('Finish!');

A saída será:

Test start...
Test finish...
Finish!
Eduardo Cuomo
fonte