Dados os exemplos a seguir, por que é outerScopeVar
indefinido em todos os casos?
var outerScopeVar;
var img = document.createElement('img');
img.onload = function() {
outerScopeVar = this.width;
};
img.src = 'lolcat.png';
alert(outerScopeVar);
var outerScopeVar;
setTimeout(function() {
outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);
// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
outerScopeVar = response;
});
alert(outerScopeVar);
// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
outerScopeVar = data;
});
console.log(outerScopeVar);
// with promises
var outerScopeVar;
myPromise.then(function (response) {
outerScopeVar = response;
});
console.log(outerScopeVar);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);
Por que é exibido undefined
em todos esses exemplos? Não quero soluções alternativas, quero saber por que isso está acontecendo.
Nota: Esta é uma pergunta canônica para a assincronicidade do JavaScript . Sinta-se livre para melhorar esta questão e adicionar exemplos mais simplificados com os quais a comunidade possa se identificar.
javascript
asynchronous
Fabrício Matté
fonte
fonte
Respostas:
Resposta de uma palavra: assincronicidade .
Prefácio
Este tópico foi iterado pelo menos algumas milhares de vezes, aqui, no Stack Overflow. Por isso, primeiro, gostaria de destacar alguns recursos extremamente úteis:
A resposta do @Felix Kling para "Como faço para retornar a resposta de uma chamada assíncrona?" . Veja sua excelente resposta explicando fluxos síncronos e assíncronos, bem como a seção "Reestruturar código".
@Benjamin Gruenbaum também fez um grande esforço para explicar a assincronicidade no mesmo tópico.
A resposta de @Matt Esch para "Obter dados de fs.readFile" também explica extremamente bem a assincronicidade de uma maneira simples.
A resposta para a pergunta em questão
Vamos traçar o comportamento comum primeiro. Em todos os exemplos, o
outerScopeVar
é modificado dentro de uma função . Essa função claramente não é executada imediatamente, está sendo atribuída ou passada como argumento. É isso que chamamos de retorno de chamada .Agora, a pergunta é: quando é que esse retorno de chamada é chamado?
Depende do caso. Vamos tentar rastrear algum comportamento comum novamente:
img.onload
pode ser chamado em algum momento no futuro , quando (e se) a imagem tiver sido carregada com êxito.setTimeout
pode ser chamado em algum momento no futuro , após o atraso expirar e o tempo limite não ter sido cancelado porclearTimeout
. Nota: mesmo quando usado0
como atraso, todos os navegadores têm um limite mínimo de atraso de tempo limite (especificado para 4ms na especificação HTML5).$.post
O retorno de chamada do jQuery pode ser chamado em algum momento no futuro , quando (e se) a solicitação do Ajax for concluída com êxito.fs.readFile
podem ser chamados em algum momento no futuro , quando o arquivo tiver sido lido com êxito ou ocorrer um erro.Em todos os casos, temos um retorno de chamada que pode ser executado em algum momento no futuro . Este "em algum momento no futuro" é o que chamamos de fluxo assíncrono .
A execução assíncrona é enviada para fora do fluxo síncrono. Ou seja, o código assíncrono será nunca executado enquanto a pilha de códigos síncronos estiver em execução. Este é o significado do JavaScript ser de thread único.
Mais especificamente, quando o mecanismo JS estiver ocioso - não executando uma pilha de (a) código síncrono - ele pesquisará eventos que podem ter acionado retornos de chamada assíncronos (por exemplo, tempo limite expirado, resposta de rede recebida) e os executará um após o outro. Isto é considerado como loop de eventos .
Ou seja, o código assíncrono destacado nas formas vermelhas desenhadas à mão pode ser executado somente após a execução de todo o código síncrono restante em seus respectivos blocos de código:
Em resumo, as funções de retorno de chamada são criadas de forma síncrona, mas executadas de forma assíncrona. Você simplesmente não pode confiar na execução de uma função assíncrona até saber que ela foi executada e como fazer isso?
É simples, realmente. A lógica que depende da execução da função assíncrona deve ser iniciada / chamada de dentro dessa função assíncrona. Por exemplo, movendo-se os
alert
s econsole.log
é também dentro da função de retorno de saída seria o resultado esperado, porque o resultado está disponível naquele momento.Implementando sua própria lógica de retorno de chamada
Geralmente, você precisa fazer mais coisas com o resultado de uma função assíncrona ou fazer coisas diferentes com o resultado, dependendo de onde a função assíncrona foi chamada. Vamos abordar um exemplo um pouco mais complexo:
Nota: eu estou usando
setTimeout
com um atraso aleatório como uma função assíncrona genérico, o mesmo exemplo se aplica ao Ajax,readFile
,onload
e qualquer outro fluxo assíncrono.Este exemplo sofre claramente do mesmo problema que os outros exemplos, não está aguardando até que a função assíncrona seja executada.
Vamos resolver isso implementando um sistema de retorno de chamada próprio. Primeiro, nos livramos daquele feio
outerScopeVar
que é completamente inútil neste caso. Em seguida, adicionamos um parâmetro que aceita um argumento de função, nosso retorno de chamada. Quando a operação assíncrona termina, chamamos esse retorno de chamada passando o resultado. A implementação (leia os comentários em ordem):Fragmento de código do exemplo acima:
Geralmente, em casos de uso reais, a API do DOM e a maioria das bibliotecas já fornecem a funcionalidade de retorno de chamada (o
helloCatAsync
implementação neste exemplo demonstrativo). Você só precisa passar a função de retorno de chamada e entender que ela será executada fora do fluxo síncrono e reestruturar seu código para acomodar isso.Você também notará que, devido à natureza assíncrona, é impossível
return
um valor de um fluxo assíncrono para o fluxo síncrono em que o retorno de chamada foi definido, pois os retornos de chamada assíncronos são executados muito tempo após o código síncrono já ter terminado de executar.Em vez de
return
inserir um valor de um retorno de chamada assíncrono, você precisará usar o padrão de retorno de chamada ou ... Promessas.Promessas
Embora existam maneiras de manter o retorno de chamada muito distante do JS vanilla, as promessas estão crescendo em popularidade e atualmente estão sendo padronizadas no ES6 (consulte Promise - MDN ).
As promessas (também conhecidas como futuros) fornecem uma leitura mais linear e, portanto, agradável do código assíncrono, mas a explicação de toda a sua funcionalidade está fora do escopo desta questão. Em vez disso, deixarei esses excelentes recursos para os interessados:
Mais material de leitura sobre assincronicidade JavaScript
fonte
A resposta de Fabrício é imediata; mas eu queria complementar sua resposta com algo menos técnico, que se concentra em uma analogia para ajudar a explicar o conceito de assincronicidade .
Uma Analogia ...
Ontem, o trabalho que eu estava realizando exigiu algumas informações de um colega. Telefonei para ele; aqui está como foi a conversa:
Nesse ponto, desliguei o telefone. Como eu precisava de informações de Bob para concluir o meu relatório, deixei o relatório e fui tomar um café, depois peguei um email. 40 minutos depois (Bob é lento), Bob ligou de volta e me deu as informações que eu precisava. Nesse ponto, retomei meu trabalho com meu relatório, pois possuía todas as informações necessárias.
Imagine se a conversa tivesse sido assim;
E eu sentei lá e esperei. E esperou. E esperou. Por 40 minutos Não fazendo nada além de esperar. Eventualmente, Bob me deu as informações, desligamos e concluí meu relatório. Mas eu tinha perdido 40 minutos de produtividade.
Este é um comportamento assíncrono vs. síncrono
É exatamente isso que está acontecendo em todos os exemplos de nossa pergunta. Carregar uma imagem, carregar um arquivo do disco e solicitar uma página via AJAX são operações lentas (no contexto da computação moderna).
Em vez de aguardar a conclusão dessas operações lentas, o JavaScript permite registrar uma função de retorno de chamada que será executada quando a operação lenta for concluída. Enquanto isso, no entanto, o JavaScript continuará executando outro código. O fato de o JavaScript executar outro código enquanto aguarda a conclusão da operação lenta torna o comportamento assíncrono . Se o JavaScript esperasse a conclusão da operação antes de executar qualquer outro código, isso teria sido um comportamento síncrono .
No código acima, estamos solicitando o carregamento do JavaScript
lolcat.png
, que é uma operação sloooow . A função de retorno de chamada será executada assim que essa operação lenta for concluída, mas, enquanto isso, o JavaScript continuará processando as próximas linhas de código; iealert(outerScopeVar)
.É por isso que vemos o alerta sendo exibido
undefined
; já que oalert()
é processado imediatamente, e não depois que a imagem foi carregada.Para corrigir nosso código, tudo o que precisamos fazer é mover o
alert(outerScopeVar)
código para a função de retorno de chamada. Como consequência disso, não precisamos mais daouterScopeVar
variável declarada como variável global.Você sempre verá que um retorno de chamada é especificado como uma função, porque essa é a única maneira * no JavaScript de definir algum código, mas não executá-lo até mais tarde.
Portanto, em todos os nossos exemplos, o
function() { /* Do something */ }
é o retorno de chamada; Para corrigir todos os exemplos, basta mover o código que precisa da resposta da operação para lá!* Tecnicamente você pode usar
eval()
também, maseval()
é mau para esse fimComo faço para manter minha ligação em espera?
No momento, você pode ter algum código semelhante a este;
No entanto, agora sabemos que isso
return outerScopeVar
acontece imediatamente; antes que aonload
função de retorno de chamada atualize a variável Isso leva aogetWidthOfImage()
retornoundefined
e aoundefined
alerta.Para corrigir isso, precisamos permitir que a chamada de função
getWidthOfImage()
registre um retorno de chamada e, em seguida, mova o alerta da largura para dentro desse retorno de chamada;... como antes, observe que conseguimos remover as variáveis globais (neste caso
width
).fonte
Aqui está uma resposta mais concisa para as pessoas que procuram uma referência rápida, bem como alguns exemplos usando promessas e assíncrono / espera.
Comece com a abordagem ingênua (que não funciona) para uma função que chama um método assíncrono (neste caso
setTimeout
) e retorna uma mensagem:undefined
é registrado nesse caso porquegetMessage
retorna antes que osetTimeout
retorno de chamada seja chamado e seja atualizadoouterScopeVar
.As duas principais maneiras de resolvê-lo estão usando retornos de chamada e promessas :
Retornos de chamada
A mudança aqui é que
getMessage
aceita umcallback
parâmetro que será chamado para entregar os resultados de volta ao código de chamada assim que disponível.Promessas
As promessas fornecem uma alternativa mais flexível que os retornos de chamada, porque eles podem ser combinados naturalmente para coordenar várias operações assíncronas. A Promises / A + implementação padrão é nativamente previsto no node.js (0.12+) e muitos navegadores atuais, mas também é implementado em bibliotecas como Bluebird e Q .
jQuery Deferreds
O jQuery fornece funcionalidade semelhante às promessas com seus adiados.
assíncrono / aguardar
Se o seu ambiente JavaScript incluir suporte para (
async
eawait
como o Node.js 7.6+), você poderá usar as promessas de forma síncrona nasasync
funções:fonte
function getMessage(param1, param2, callback) {...}
.async/await
amostra, mas estou com problemas. Em vez de instanciar anew Promise
, estou fazendo uma.Get()
chamada e, portanto, não tenho acesso a nenhumresolve()
método. Assim, o meugetMessage()
está retornando a promessa e não o resultado. Você poderia editar sua resposta um pouco para mostrar uma sintaxe funcional para isso?.Get()
ligação. Provavelmente é melhor postar uma nova pergunta.Para afirmar o óbvio, o copo representa
outerScopeVar
.Funções assíncronas são como ...
fonte
As outras respostas são excelentes e eu só quero fornecer uma resposta direta a isso. Limitando apenas as chamadas assíncronas do jQuery
Todas as chamadas ajax (incluindo o
$.get
ou$.post
ou$.ajax
) são assíncronas.Considerando o seu exemplo
A execução do código começa na linha 1, declara a variável e dispara e chamada assíncrona na linha 2 (ou seja, a solicitação de postagem) e continua sua execução da linha 3, sem aguardar que a solicitação de postagem conclua sua execução.
Digamos que a solicitação de postagem leva 10 segundos para ser concluída, o valor de
outerScopeVar
somente será definido após esses 10 segundos.Tentar,
Agora, ao executar isso, você receberá um alerta na linha 3. Agora aguarde um pouco até ter certeza de que a solicitação de postagem retornou algum valor. Quando você clicar em OK, na caixa de alerta, o próximo alerta imprimirá o valor esperado, porque você o aguardou.
No cenário da vida real, o código se torna,
Todo o código que depende das chamadas assíncronas é movido para dentro do bloco assíncrono ou aguardando as chamadas assíncronas.
fonte
or by waiting on the asynchronous calls
Como é que alguém faz isso?Em todos esses cenários,
outerScopeVar
é modificado ou atribuído um valor de forma assíncrona ou acontecendo posteriormente (aguardando ou ouvindo a ocorrência de algum evento), pelo qual a execução atual não aguardará . Portanto, em todos esses casos, o fluxo de execução atual resulta emouterScopeVar = undefined
Vamos discutir cada exemplo (marquei a parte que é chamada de forma assíncrona ou atrasada para a ocorrência de alguns eventos):
1
Aqui registramos um eventlistner que será executado nesse evento específico. Aqui está o carregamento da imagem. Em seguida, a execução atual continua nas próximas linhas
img.src = 'lolcat.png';
e,alert(outerScopeVar);
enquanto isso, o evento pode não ocorrer. isto é,img.onload
espera a função para carregar a imagem referida de forma assíncrona. Isso acontecerá em todos os exemplos a seguir - o evento pode ser diferente.2)
Aqui, o evento de tempo limite desempenha a função, que chamará o manipulador após o tempo especificado. Aqui está
0
, mas ainda assim registra um evento assíncrono que será adicionado à última posição daEvent Queue
execução, o que torna o atraso garantido.3)
Desta vez, retorno de chamada ajax.
4)
O nó pode ser considerado um rei da codificação assíncrona. Aqui a função marcada é registrada como um manipulador de retorno de chamada que será executado após a leitura do arquivo especificado.
5)
A promessa óbvia (algo será feito no futuro) é assíncrona. consulte Quais são as diferenças entre adiado, promissor e futuro em JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
fonte