desenv-web-rp.com

Como faço para retornar a resposta de uma chamada assíncrona?

Eu tenho uma função foo que faz uma requisição Ajax. Como posso retornar a resposta de foo?

Eu tentei retornar o valor do retorno de chamada success, bem como atribuir a resposta a uma variável local dentro da função e retorná-la, mas nenhuma dessas maneiras realmente retorna a resposta.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.
4904
Felix Kling

→ Para uma explicação mais geral do comportamento assíncrono com exemplos diferentes, por favor vejaPor que minha variável é inalterada depois que eu a modifico dentro de uma função? - Referência de código assíncrona

→ Se você já entendeu o problema, pule para as possíveis soluções abaixo.

O problema

OAin Ajax significa assíncrono . Isso significa enviar o pedido (ou receber a resposta) é retirado do fluxo de execução normal. No seu exemplo, $.ajax retorna imediatamente e a próxima instrução, return result;, é executada antes que a função que você passou como retorno de chamada success seja chamada.

Aqui está uma analogia que, esperamos, faz a diferença entre o fluxo síncrono e assíncrono mais claro:

Síncrono

Imagine que você faça uma ligação para um amigo e peça a ele que procure algo para você. Embora possa demorar um pouco, você espera no telefone e olha para o espaço, até que seu amigo lhe dê a resposta que você precisa.

O mesmo acontece quando você faz uma chamada de função contendo código "normal":

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

Mesmo que findItem possa levar muito tempo para executar, qualquer código vindo depois de var item = findItem(); tem que wait até que a função retorne o resultado.

Assíncrono

Você liga para seu amigo novamente pelo mesmo motivo. Mas desta vez você diz a ele que está com pressa e ele deve ligar para você no seu celular. Você desliga, sai de casa e faz o que você planejou fazer. Depois que seu amigo ligar de volta, você está lidando com as informações que ele lhe deu.

É exatamente o que está acontecendo quando você faz um pedido Ajax.

findItem(function(item) {
    // Do something with item
});
doSomethingElse();

Em vez de aguardar a resposta, a execução continua imediatamente e a instrução após a execução da chamada Ajax é executada. Para obter a resposta eventualmente, você fornece uma função a ser chamada assim que a resposta for recebida, um retorno de chamada (note algo? Retorno de chamada?). Qualquer declaração que venha depois dessa chamada é executada antes do callback ser chamado.


Solução (s)

Aceite a natureza assíncrona do JavaScript! Enquanto certas operações assíncronas fornecem contrapartes síncronas (assim como "Ajax"), geralmente é desencorajado usá-las, especialmente em um contexto de navegador.

Por que é ruim você pergunta?

O JavaScript é executado no thread da interface do usuário do navegador e qualquer processo de execução longa bloqueará a interface do usuário, deixando-a sem resposta. Além disso, há um limite superior no tempo de execução para o JavaScript, e o navegador perguntará ao usuário se continuará a execução ou não.

Tudo isso é uma experiência ruim para o usuário. O usuário não será capaz de dizer se tudo está funcionando bem ou não. Além disso, o efeito será pior para usuários com uma conexão lenta.

A seguir, veremos três soluções diferentes que estão sendo construídas umas sobre as outras:

  • Promessas com async/await(ES2017 +, disponível em navegadores mais antigos se você usar um transpilador ou regenerador)
  • Callbacks (popular no nó)
  • Promessas com then()(ES2015 +, disponível em navegadores mais antigos se você usar uma das muitas bibliotecas prometidas)

Todos os três estão disponíveis nos navegadores atuais e no nó 7+.


ES2017 +: Promessas com async/await

A versão do ECMAScript lançada em 2017 introduziu suporte ao nível de sintaxe para funções assíncronas. Com a ajuda de async e await, você pode escrever assíncrono em um "estilo síncrono". O código ainda é assíncrono, mas é mais fácil de entender.

async/await baseia-se em promessas: uma função async sempre retorna uma promessa. await "desembrulha" uma promessa e resulta no valor com o qual a promessa foi resolvida ou lança um erro se a promessa foi rejeitada.

Importante: Você só pode usar await dentro de uma função async. No momento, o await de nível superior ainda não é suportado, portanto, talvez seja necessário fazer um IIFE assíncrono ( Expressão de Função Invocada Imediatamente ) para iniciar um contexto async.

Você pode ler mais sobre async e await em MDN.

Aqui está um exemplo que se baseia no atraso acima:

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}


async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

Atual navegador e versões suportam async/await. Você também pode suportar ambientes mais antigos, transformando seu código em ES5 com a ajuda de regenerator (ou ferramentas que usam o regenerador, como Babel ).


Deixe funções aceitar retornos de chamada

Um retorno de chamada é simplesmente uma função passada para outra função. Essa outra função pode chamar a função passada sempre que estiver pronta. No contexto de um processo assíncrono, o retorno de chamada será chamado sempre que o processo assíncrono for concluído. Normalmente, o resultado é passado para o retorno de chamada.

No exemplo da pergunta, você pode fazer foo aceitar um retorno de chamada e usá-lo como retorno de chamada success. Então, é isso

var result = foo();
// Code that depends on 'result'

torna-se

foo(function(result) {
    // Code that depends on 'result'
});

Aqui nós definimos a função "inline", mas você pode passar qualquer referência de função:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo propriamente dito é definido da seguinte forma:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback irá se referir à função que passamos para foo quando nós a chamamos e simplesmente a passamos para success. Ou seja uma vez que o pedido do Ajax é bem-sucedido, $.ajax chamará callback e passará a resposta para o retorno de chamada (que pode ser referido com result, pois é assim que definimos o retorno de chamada).

Você também pode processar a resposta antes de transmiti-la ao retorno de chamada:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

É mais fácil escrever código usando callbacks do que parece. Afinal, o JavaScript no navegador é fortemente orientado a eventos (eventos DOM). Receber a resposta do Ajax não é mais do que um evento.
Dificuldades podem surgir quando você precisa trabalhar com código de terceiros, mas a maioria dos problemas pode ser resolvida apenas pensando no fluxo do aplicativo.


ES2015 +: Promessas com então ()

O Promise API é um novo recurso do ECMAScript 6 (ES2015), mas tem um bom suporte ao navegador já. Há também muitas bibliotecas que implementam a API Promises padrão e fornecem métodos adicionais para facilitar o uso e a composição de funções assíncronas (por exemplo, bluebird ).

Promessas são contêineres para valores future. Quando a promessa recebe o valor (é resolvido) ou quando é cancelado (rejeitado), ele notifica todos os seus "ouvintes" que desejam acessar este valor.

A vantagem sobre os retornos de chamada simples é que eles permitem que você desvincule seu código e eles são mais fáceis de compor.

Aqui está um exemplo simples de usar uma promessa:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected 
    // (it would not happen in this example, since `reject` is not called).
  });

Aplicado à nossa chamada Ajax, poderíamos usar promessas como esta:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("/echo/json")
  .then(function(result) {
    // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });

Descrever todas as vantagens que prometem oferecer está além do escopo desta resposta, mas se você escrever um novo código, você deve considerá-las seriamente. Eles fornecem uma grande abstração e separação do seu código.

Mais informações sobre promessas: HTML5 rocks - JavaScript Promises

Nota lateral: os objetos adiados do jQuery

Objetos adiados são a implementação customizada de promessas do jQuery (antes da padronização da API do Promise). Eles se comportam quase como promessas, mas expõem uma API ligeiramente diferente.

Todo método Ajax do jQuery já retorna um "objeto adiado" (na verdade, uma promessa de um objeto adiado) que você pode simplesmente retornar da sua função:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});

Nota lateral: Pegadinhas de promessa

Tenha em mente que promessas e objetos adiados são apenas containers para um valor futuro, eles não são o valor em si. Por exemplo, suponha que você tenha o seguinte:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

Este código não compreende os problemas de assincronia acima. Especificamente, $.ajax() não congela o código enquanto ele verifica a página '/ password' no seu servidor - ele envia uma requisição para o servidor e enquanto espera, imediatamente retorna um objeto jQuery Ajax Adiado, não a resposta do servidor. Isso significa que a instrução if sempre obterá esse objeto Deferred, tratá-lo como true e proceder como se o usuário estivesse logado. Não é bom.

Mas a correção é fácil:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});

Não recomendado: chamadas "Ajax" síncronas

Como mencionei, algumas (!) Operações assíncronas têm contrapartes síncronas. Eu não defendo o uso deles, mas por questões de integridade, aqui está como você executaria uma chamada síncrona:

Sem jQuery

Se você usar diretamente um XMLHTTPRequest object, passe false como terceiro argumento para .open .

jQuery

Se você usar jQuery , você pode definir a opção async como false. Note que esta opção é depreciada desde a jQuery 1.8. Você pode então usar um retorno de chamada success ou acessar a propriedade responseText do objeto jqXHR :

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

Se você usar qualquer outro método do jQuery Ajax, como $.get, $.getJSON, etc., você deve alterá-lo para $.ajax (já que você só pode passar parâmetros de configuração para $.ajax).

Heads up! Não é possível fazer uma solicitação síncrona JSONP . O JSONP, por sua própria natureza, é sempre assíncrono (mais uma razão para não considerar essa opção).

5203
Felix Kling

Se você está não usando jQuery no seu código, esta resposta é para você

Seu código deve ser algo ao longo das linhas deste:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Felix Kling fez um ótimo trabalho escrevendo uma resposta para pessoas que usam o jQuery para AJAX, e decidi oferecer uma alternativa para pessoas que não são.

( Note, para aqueles que usam a nova API fetch, Angular ou promessas eu adicionei outra resposta abaixo )


O que você está enfrentando

Este é um breve resumo de "Explicação do problema" da outra resposta, se você não tiver certeza depois de ler isto, leia isso.

OAin AJAX significa assíncrono . Isso significa enviar o pedido (ou receber a resposta) é retirado do fluxo de execução normal. No seu exemplo, .send retorna imediatamente e a próxima instrução, return result;, é executada antes que a função que você passou como success callback seja chamada.

Isso significa que, quando você retornar, o ouvinte que você definiu ainda não foi executado, o que significa que o valor que você está retornando não foi definido.

Aqui está uma analogia simples

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(violino)

O valor de a retornado é undefined desde que a parte a=5 ainda não tenha sido executada. AJAX age assim, você está retornando o valor antes que o servidor tenha a chance de informar ao seu navegador qual é esse valor.

Uma possível solução para esse problema é codificar re-ativamente, informando ao seu programa o que fazer quando o cálculo for concluído.

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

Isso é chamado CPS . Basicamente, estamos passando getFive uma ação para executar quando estiver concluída, estamos dizendo ao nosso código como reagir quando um evento é concluído (como nossa chamada AJAX ou, neste caso, o tempo limite).

O uso seria:

getFive(onComplete);

Qual deve alertar "5" para a tela. (violino) .

Soluções possíveis

Existem basicamente duas maneiras de resolver isso:

  1. Faça a chamada AJAX síncrona (vamos chamá-lo de SJAX).
  2. Reestruture seu código para funcionar corretamente com retornos de chamada.

1. Synchronous AJAX - Não faça isso !!

Quanto ao AJAX síncrono, não faça isso! A resposta de Felix levanta alguns argumentos convincentes sobre por que é uma má ideia. Para resumir, ele congelará o navegador do usuário até que o servidor retorne a resposta e crie uma experiência de usuário muito ruim. Aqui está outro breve resumo do MDN sobre o porquê:

XMLHttpRequest suporta comunicações síncronas e assíncronas. Em geral, no entanto, as solicitações assíncronas devem ser preferidas às solicitações síncronas por motivos de desempenho.

Em suma, as solicitações síncronas bloqueiam a execução do código ... ... isso pode causar problemas sérios ...

Se você have para fazê-lo, você pode passar um sinalizador: Aqui está como:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. Reestruturar código

Deixe sua função aceitar um retorno de chamada. No código de exemplo, foo pode ser feito para aceitar um retorno de chamada. Nós estaremos dizendo ao nosso código como react quando foo for concluído.

Assim:

var result = foo();
// code that depends on `result` goes here

Torna-se:

foo(function(result) {
    // code that depends on `result`
});

Aqui nós passamos uma função anônima, mas podemos facilmente passar uma referência para uma função existente, fazendo com que pareça:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

Para obter mais detalhes sobre como esse tipo de design de retorno de chamada é feito, verifique a resposta de Felix.

Agora, vamos definir o próprio foo para agir de acordo

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(violino)

Nós agora fizemos nossa função foo aceitar uma ação para executar quando o AJAX é completado com sucesso, nós podemos estender isto mais verificando se o status da resposta não é 200 e agindo de acordo (criar um manipulador de falha e tal). Solucionando efetivamente nosso problema.

Se você ainda está tendo dificuldades em entender isto leia o AJAX guia de introdução no MDN.

1002
Benjamin Gruenbaum

XMLHttpRequest 2 (em primeiro lugar leia as respostas de Benjamin Gruenbaum & Felix Kling )

Se você não usa o jQuery e quer um pequeno XMLHttpRequest 2 que funcione nos navegadores modernos e também nos navegadores móveis, sugiro usá-lo desta maneira:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

Como você pode ver:

  1. É mais curto que todas as outras funções listadas.
  2. O retorno de chamada é definido diretamente (portanto, nenhum fechamento desnecessário extra).
  3. Ele usa o novo onload (para que você não tenha que verificar o estado do readyState &&)
  4. Existem algumas outras situações que não me lembro que tornam o XMLHttpRequest 1 irritante.

Existem duas maneiras de obter a resposta desta chamada Ajax (três usando o nome var XMLHttpRequest):

O mais simples:

this.response

Ou se por algum motivo você bind() o retorno de chamada para uma classe:

e.target.response

Exemplo:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

Ou (o acima é melhor funções anônimas são sempre um problema):

ajax('URL', function(e){console.log(this.response)});

Nada mais fácil.

Agora, algumas pessoas provavelmente dirão que é melhor usar onreadystatechange ou até mesmo o nome da variável XMLHttpRequest. Isto é errado.

Check out XMLHttpRequest recursos avançados

Ele suportava todos os navegadores modernos. E eu posso confirmar como estou usando essa abordagem desde XMLHttpRequest 2 existe. Eu nunca tive nenhum tipo de problema em todos os navegadores que uso.

onreadystatechange só é útil se você quiser obter os cabeçalhos no estado 2.

Usar o nome da variável XMLHttpRequest é outro grande erro, já que você precisa executar o callback dentro dos closures onload/oreadystatechange, senão você o perdeu.


Agora, se você quiser algo mais complexo usando post e FormData, pode facilmente estender essa função:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

Mais uma vez ... é uma função muito curta, mas não consegue postar.

Exemplos de uso:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

Ou passe um elemento de formulário completo (document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

Ou defina alguns valores personalizados:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

Como você pode ver, eu não implantei a sincronização ... é uma coisa ruim.

Tendo dito isso ... por que não fazer isso da maneira mais fácil?


Como mencionado no comentário, o uso de error & synchronous rompe completamente o ponto da resposta. Qual é um bom caminho curto para usar o Ajax da maneira correta?

Error handler

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

No script acima, você tem um manipulador de erros que é estaticamente definido para não comprometer a função. O manipulador de erros também pode ser usado para outras funções.

Mas, para realmente obter um erro, o only way é escrever um URL errado e, nesse caso, cada navegador gera um erro.

Os manipuladores de erros talvez sejam úteis se você definir cabeçalhos personalizados, definir o responseType como buffer de matriz de blob ou o que for ...

Mesmo se você passar 'POSTAPAPAP' como o método, não lançará um erro.

Mesmo se você passar 'fdggdgilfdghfldj' como formdata, isso não causará um erro.

No primeiro caso, o erro está dentro do displayAjax() em this.statusText como Method not Allowed.

No segundo caso, simplesmente funciona. Você tem que verificar no lado do servidor, se você passou os dados do post direito.

domínio cruzado não permitido gera erro automaticamente.

Na resposta de erro, não há códigos de erro.

Existe apenas o this.type que está configurado para erro.

Por que adicionar um manipulador de erros se você não tem controle sobre erros? A maioria dos erros é retornada dentro desta função de retorno de chamada displayAjax().

Portanto, não há necessidade de verificações de erros se você conseguir copiar e colar o URL corretamente. ;)

PS: Como o primeiro teste eu escrevi x ('x', displayAjax) ..., e ele obteve uma resposta totalmente ... ??? Então, verifiquei a pasta onde o HTML está localizado e havia um arquivo chamado 'x.xml'. Então, mesmo se você esquecer a extensão do seu arquivo XMLHttpRequest 2 WILL FIND IT. Eu lol'd


Leia um arquivo síncrono

Não faça isso.

Se você quiser bloquear o navegador por um tempo, carregue um arquivo .txt grande e agradável de forma síncrona.

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

Agora você pode fazer

 var res = omg('thisIsGonnaBlockThePage.txt');

Não há outra maneira de fazer isso de maneira não assíncrona. (Sim, com loop setTimeout ... mas sério?)

Outro ponto é ... se você trabalha com APIs ou apenas com os arquivos da sua própria lista ou o que você sempre usa diferentes funções para cada requisição ...

Só se você tiver uma página onde você carrega sempre o mesmo XML/JSON ou o que você precisa apenas uma função. Nesse caso, modifique um pouco a função Ajax e substitua b por sua função especial.


As funções acima são para uso básico.

Se você quiser estender a função ...

Sim você pode.

Estou usando muitas APIs e uma das primeiras funções que integro em cada página HTML é a primeira função Ajax nesta resposta, com GET apenas ...

Mas você pode fazer muitas coisas com XMLHttpRequest 2:

Eu fiz um gerenciador de downloads (usando intervalos em ambos os lados com currículo, filereader, filesystem), vários conversores de redimensionamento de imagens usando canvas, preencha bancos de dados web com base64images e muito mais ... Mas nesses casos você deve criar uma função apenas para isso propósito ... às vezes você precisa de um blob, buffers de matriz, você pode definir cabeçalhos, substituir mimetype e há muito mais ...

Mas a questão aqui é como retornar uma resposta do Ajax ... (acrescentei uma maneira fácil).

363
cocco

Se você está usando promessas, esta resposta é para você.

Isso significa AngularJS, jQuery (com adiamento), substituição de XHR nativo (busca), EmberJS, salvamento do BackboneJS ou qualquer biblioteca de nó que retorne promessas.

Seu código deve ser algo ao longo das linhas deste:

function foo() {
    var data;
    // or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // result is always undefined no matter what.

Felix Kling fez um ótimo trabalho escrevendo uma resposta para pessoas que usam jQuery com callbacks para AJAX. Eu tenho uma resposta para o XHR nativo. Essa resposta é para uso genérico de promessas no frontend ou no backend.


A questão central

O modelo de simultaneidade do JavaScript no navegador e no servidor com o NodeJS/io.js é assíncrono e reativo.

Sempre que você chama um método que retorna uma promessa, os manipuladores then são always executados de forma assíncrona - ou seja, after o código abaixo deles que não está em um manipulador .then.

Isso significa que quando você retornar data, o manipulador then que você definiu ainda não foi executado. Isso, por sua vez, significa que o valor que você está retornando não foi definido para o valor correto no tempo.

Aqui está uma analogia simples para o problema:

    function getFive(){
        var data;
        setTimeout(function(){ // set a timer for one second in the future
           data = 5; // after a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

O valor de data é undefined, já que a parte data = 5 ainda não foi executada. Ele provavelmente será executado em um segundo, mas nesse momento é irrelevante para o valor retornado.

Como a operação ainda não aconteceu (AJAX, chamada do servidor, IO, timer), você está retornando o valor antes que a solicitação tenha a chance de informar ao seu código qual é esse valor.

Uma possível solução para esse problema é codificar re-ativamente, informando ao seu programa o que fazer quando o cálculo for concluído. As promessas ativamente permitem isso por serem temporais (sensíveis ao tempo) na natureza.

Recapitulação rápida de promessas

Uma Promessa é um valor ao longo do tempo. As promessas têm estado, começam como pendentes sem valor e podem se contentar com:

  • preenchido significando que o cálculo foi concluído com sucesso.
  • rejected significa que o cálculo falhou.

Uma promessa só pode mudar estados uma vez depois dos quais sempre permanecerá no mesmo estado para sempre. Você pode anexar manipuladores then para prometer extrair seu valor e manipular erros. Os manipuladores then permitem encadeamento de chamadas. As promessas são criadas por usando APIs que as retornam . Por exemplo, o mais moderno AJAX substituição fetch ou o jQuery $.get return promete.

Quando chamamos .then em uma promessa e return alguma coisa dela - obtemos uma promessa para o valor processado. Se retornarmos outra promessa, conseguiremos coisas incríveis, mas vamos segurar nossos cavalos.

Com promessas

Vamos ver como podemos resolver a questão acima com promessas. Primeiro, vamos demonstrar nossa compreensão dos estados de promessa acima usando o construtor Promise para criar uma função de atraso:

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

Agora, depois de convertermos o setTimeout para usar promessas, podemos usar then para fazer com que ele conte:

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

Basicamente, em vez de retornar um valor que não podemos fazer por causa do modelo de simultaneidade - estamos retornando um wrapper para um valor que podemos desembrulhar com then. É como uma caixa que você pode abrir com then.

Aplicando isso

Isto é o mesmo para a sua chamada API original, você pode:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // process it inside the `then`
    });
}

foo().then(function(response){
    // access the value inside the `then`
})

Então, isso funciona tão bem. Aprendemos que não podemos retornar valores de chamadas já assíncronas, mas podemos usar promessas e encadear as mesmas para executar o processamento. Agora sabemos como retornar a resposta de uma chamada assíncrona.

ES2015 (ES6)

O ES6 introduz geradores que são funções que podem retornar no meio e depois retomar o ponto em que estavam. Isso geralmente é útil para sequências, por exemplo:

function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
    yield 1;
    yield 2;
    while(true) yield 3;
}

É uma função que retorna um iterador sobre a seqüência 1,2,3,3,3,3,.... que pode ser iterada. Enquanto isso é interessante por si só e abre espaço para muita possibilidade, há um caso interessante em particular.

Se a sequência que estamos produzindo é uma seqüência de ações em vez de números - podemos pausar a função sempre que uma ação é gerada e esperar por ela antes de retomarmos a função. Então, ao invés de uma sequência de números, precisamos de uma sequência de valores futuro isto é: promessas.

Esse truque um tanto complicado, mas muito poderoso nos permite escrever um código assíncrono de maneira síncrona. Existem vários "corredores" que fazem isso para você, escrever um é um pequeno número de linhas de código, mas está além do escopo desta resposta. Eu vou usar o Promise.coroutine do Bluebird aqui, mas existem outros wrappers como co ou Q.async.

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // notice the yield
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
});

Esse método retorna uma promessa em si, que podemos consumir de outras corrotinas. Por exemplo:

var main = coroutine(function*(){
   var bar = yield foo(); // wait our earlier coroutine, it returns a promise
   // server call done here, code below executes when done
   var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
   console.log(baz); // runs after both requests done
});
main();

ES2016 (ES7)

No ES7, isso é mais padronizado, existem várias propostas agora, mas em todas elas você pode prometer await. Isso é apenas "sugar" (melhor sintaxe) para a proposta ES6 acima, adicionando as palavras-chave async e await. Fazendo o exemplo acima:

async function foo(){
    var data = await fetch("/echo/json"); // notice the await
    // code here only executes _after_ the request is done
    return data.json(); // data is defined
}

Ainda retorna uma promessa da mesma forma :)

292
Benjamin Gruenbaum

Você está usando o Ajax incorretamente. A idéia é não retornar nada, mas, em vez disso, entregar os dados para algo chamado função de retorno de chamada, que lida com os dados.

Isso é:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

Retornar qualquer coisa no manipulador de envio não fará nada. Você deve, em vez disso, entregar os dados ou fazer o que quiser diretamente dentro da função de sucesso.

230
Nic

A solução mais simples é criar uma função JavaScript e chamá-la para o callback Ajax success.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 
219
Hemant Bavle

Vou responder com uma história em quadrinhos desenhada à mão horrível. A segunda imagem é a razão pela qual result é undefined no seu exemplo de código.

 enter image description here

195
Johannes Fahrenkrug

Angular1

Para pessoas que estão usando AngularJS , podem lidar com essa situação usando Promises.

Aqui diz,

As promessas podem ser usadas para desestimular funções assíncronas e permitir a cadeia de várias funções juntas.

Você pode encontrar uma boa explicação aqui também.

Exemplo encontrado em docs mencionado abaixo.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 e mais tarde

Em Angular2, observe o seguinte exemplo, mas seu recommended para usar Observables com Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

Você pode consumir isso dessa maneira,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

Veja o original post aqui. Mas o TypeScript não suporta native es6 Promises , se você quiser usá-lo, você pode precisar de um plugin para isso.

Além disso, aqui estão as promessas spec define aqui.

143
Maleen Abewardana

A maioria das respostas aqui fornece sugestões úteis para quando você tem uma única operação assíncrona, mas às vezes, isso ocorre quando você precisa fazer uma operação assíncrona para each entry em uma matriz ou outra estrutura semelhante a lista. A tentação é fazer isso:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

Exemplo:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

A razão para isso não funcionar é que os retornos de chamada de doSomethingAsync ainda não foram executados até o momento em que você está tentando usar os resultados.

Portanto, se você tiver uma matriz (ou lista de algum tipo) e desejar executar operações assíncronas para cada entrada, terá duas opções: Executar as operações em paralelo (sobreposição) ou em série (uma após a outra em sequência).

Paralelo

Você pode iniciar todos eles e acompanhar quantos retornos de chamada você está esperando e, em seguida, usar os resultados quando receber muitos retornos de chamada:

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

Exemplo:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Poderíamos acabar com expecting e apenas usar results.length === theArray.length, mas isso nos deixa aberto para a possibilidade de que theArray seja alterado enquanto as chamadas estão em destaque ...)

Observe como usamos o index de forEach para salvar o resultado em results na mesma posição que a entrada a que se refere, mesmo se os resultados chegarem fora de ordem (já que as chamadas assíncronas não são necessariamente concluídas na ordem em que foram iniciadas ).

Mas e se você precisar return os resultados de uma função? Como as outras respostas apontaram, você não pode; você precisa ter sua função aceita e chamar um callback (ou retornar um Promise ). Aqui está uma versão de retorno:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

Exemplo:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Ou aqui está uma versão que retorna um Promise:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Claro, se doSomethingAsync nos passou erros, nós usaríamos reject para rejeitar a promessa quando recebemos um erro.)

Exemplo:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Ou alternadamente, você poderia fazer um wrapper para doSomethingAsync que retorna uma promessa, e então faça o seguinte ...)

Se doSomethingAsync lhe der um Promise , você pode usar Promise.all :

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Se você sabe que doSomethingAsync irá ignorar um segundo e terceiro argumento, você pode simplesmente passá-lo diretamente para map (map chama seu retorno de chamada com três argumentos, mas a maioria das pessoas usa apenas o primeiro a maior parte do tempo):

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Exemplo:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Note que Promise.all resolve sua promessa com uma matriz dos resultados de todas as promessas que você dá quando todas elas são resolvidas, ou rejeita sua promessa quando o primeiro das promessas que você dá rejeita.

Series

Suponha que você não queira que as operações estejam em paralelo? Se você quiser executá-los um após o outro, precisará aguardar a conclusão de cada operação antes de iniciar a próxima. Aqui está um exemplo de uma função que faz isso e chama um retorno de chamada com o resultado:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(Como estamos fazendo o trabalho em série, podemos usar results.Push(result) já que sabemos que não obteremos resultados fora de ordem. Acima poderíamos ter usado results[index] = result;, mas em alguns dos exemplos a seguir não usamos não tem um índice para usar.)

Exemplo:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.Push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Ou, novamente, crie um wrapper para doSomethingAsync que lhe dê uma promessa e faça o seguinte ...)

Se doSomethingAsync lhe der um Promise, se você puder usar a sintaxe ES2017 + (talvez com um transpilador como Babel ), você pode usar uma função async com for-of e await :

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Exemplo:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.Push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Se você não pode usar a sintaxe ES2017 + (ainda), você pode usar uma variação no padrão "Promise reduce" (isso é mais complexo do que o usual Promise reduce porque não estamos passando o resultado de um para o em seguida, mas reunindo os resultados em uma matriz):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Exemplo:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.Push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... o que é menos complicado com ES2015 + arrow functions :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Exemplo:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.Push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}
122
T.J. Crowder

Dê uma olhada neste exemplo:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Como você pode ver, getJoke é retornando a resolved promise (é resolvido ao retornar res.data.value). Então você espera até que o $ http.get request seja concluído e, em seguida, console.log (res.joke) seja executado (como um fluxo assíncrono normal).

Este é o plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

caminho ES6 (async - await)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
97
Francisco Carmona

Outra abordagem para retornar um valor de uma função assíncrona é passar em um objeto que armazenará o resultado da função assíncrona.

Aqui está um exemplo do mesmo:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.Push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

Eu estou usando o objeto result para armazenar o valor durante a operação assíncrona. Isso permite que o resultado esteja disponível mesmo após o trabalho assíncrono.

Eu uso muito essa abordagem. Eu estaria interessado em saber o quão bem esta abordagem funciona onde a ligação do resultado através de módulos consecutivos está envolvida.

85
jsbisht

Este é um dos lugares que duas formas de ligação de dados que é usado em muitos novos frameworks JavaScript vai funcionar muito para você ...

Então, se você estiver usando Angular, React ou quaisquer outros frameworks que façam duas formas de ligação de dados, este problema é simplesmente corrigido para você, então no Word fácil, seu resultado é undefined no primeiro estágio, então você tem result = undefined antes de receber os dados, então assim que você obtiver o resultado, ele será atualizado e será atribuído ao novo valor que é a resposta de sua chamada Ajax ...

Mas como você pode fazê-lo em javascript ou jQuery por exemplo, como você fez nesta pergunta?

Você pode usar um callback , promise e recentemente observable para lidar com isso para você, por exemplo em promessas que temos alguma função como success () ou then () que será executado quando seus dados estiverem prontos para você, mesmo com callback ou subscribe function em observable .

Por exemplo, no seu caso em que você está usando jQuery , você pode fazer algo assim:

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); //after we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); //fooDone has the data and console.log it
    };

    foo(); //call happens here
});

Para mais informações sobre o estudo promessas e observables que são maneiras mais recentes de fazer este material assíncrono.

80
Alireza

Enquanto promessas e retornos de chamada funcionam bem em muitas situações, é uma dor na parte traseira para expressar algo como:

if (!name) {
  name = async1();
}
async2(name);

Você acabaria passando por async1; verifique se name é indefinido ou não e chame o callback de acordo.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

Embora seja okay em pequenos exemplos, fica irritante quando você tem muitos casos semelhantes e manipulação de erros envolvidos.

Fibers ajuda a resolver o problema.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Você pode verificar o projeto aqui .

77
rohithpr

Resposta curta é, você tem que implementar um retorno de chamada como este:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
73
Pablo Matias Gomez

O exemplo a seguir que escrevi mostra como

  • Manipular chamadas HTTP assíncronas;
  • Aguarde a resposta de cada chamada da API;
  • Use Promise pattern;
  • Use Promise.all pattern para unir várias chamadas HTTP;

Este exemplo de trabalho é autônomo. Ele definirá um objeto de solicitação simples que usa o objeto XMLHttpRequest da janela para fazer chamadas. Ele definirá uma função simples para aguardar a conclusão de várias promessas.

Contexto. O exemplo é consultar o ponto de extremidade Spotify Web API para procurar objetos playlist para um determinado conjunto de sequências de consulta:

[
 "search?type=playlist&q=%22Doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Para cada item, um novo Promise disparará um bloco - ExecutionBlock, analisará o resultado, agendará um novo conjunto de promessas com base no array resultante, que é uma lista de objetos Spotify user e executará a nova chamada HTTP dentro de ExecutionProfileBlock de forma assíncrona.

Em seguida, você pode ver uma estrutura Promise aninhada, que permite gerar chamadas HTTP aninhadas múltiplas e completamente assíncronas e associar os resultados de cada subconjunto de chamadas por Promise.all.

NOTEAPIs recentes do Spotify search exigirão que um token de acesso seja especificado nos cabeçalhos da solicitação:

-H "Authorization: Bearer {your access token}" 

Então, para executar o exemplo a seguir, você precisa colocar seu token de acesso nos cabeçalhos de solicitação:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.Push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22Doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

Eu tenho discutido extensivamente esta solução aqui .

70
loretoparisi

Resposta de 2017: agora você pode fazer exatamente o que deseja em todos os navegadores e nós atuais

Isso é bem simples:

  • Devolva uma promessa
  • Use o 'await' , que dirá ao JavaScript para aguardar a promessa de ser resolvido em um valor (como a resposta HTTP)
  • Adicione a palavra-chave 'async' à função pai

Aqui está uma versão funcional do seu código:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

await é suportado em todos os navegadores atuais e nó 8

67
mikemaccana

Você pode usar essa biblioteca personalizada (escrita usando o Promise) para fazer uma chamada remota.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Exemplo de uso simples:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
60
Vinoth Rajendran

Js é um único encadeado.

O navegador pode ser dividido em três partes:

1) Evento Loop

2) API da Web

3) Fila de Eventos

Event Loop é executado para sempre ou seja, tipo de loop infinito.Event Queue é onde todas as suas funções são enviadas para algum evento (exemplo: click) este é um por um realizado fora da fila e colocado em Event loop que executa esta função e se prepara para o próximo, após o primeiro ser executado. Isso significa que a execução de uma função não é iniciada até que a função antes dela seja executada no loop de eventos.

Agora vamos pensar que nós empurramos duas funções em uma fila, uma é para obter um dado do servidor e outra utiliza esse dado. Nós pressionamos a função serverRequest () na fila primeiro e depois a função utiliseData (). A função serverRequest entra no loop de eventos e faz uma chamada para o servidor, pois nunca sabemos quanto tempo levará para obter dados do servidor, portanto, é esperado que esse processo leve tempo e, por isso, ocupamos nosso loop de eventos. A API entra em função, toma essa função do loop de eventos e lida com o servidor tornando o loop de eventos livre para que possamos executar a próxima função da fila.A próxima função na fila é utiliseData () que entra em loop, mas não há dados disponíveis desperdício e execução da próxima função continua até o final da fila (isso é chamado de chamada assíncrona, ou seja, podemos fazer outra coisa até obtermos os dados)

Vamos supor que nossa função serverRequest () tenha uma instrução de retorno em um código, quando recebermos de volta os dados da API da Web do servidor, Empurraremos na fila no final da fila. À medida que é empurrado no final da fila, não podemos utilizar seus dados, pois não há mais nenhuma função em nossa fila para utilizar esses dados. Assim, não é possível retornar algo da chamada assíncrona.

Assim, a solução para isso é callback ou promessa.

Uma imagem de uma das respostas aqui, explica corretamente o uso do retorno de chamada ... Damos à nossa função (função utilizando dados retornados do servidor) para função de servidor de chamada.

CallBack

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

No meu código é chamado como

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

Leia aqui os novos métodos no ECMA (2016/17) para fazer chamadas assíncronas (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

55
Aniket Jha

Outra solução é executar código via executor sequencial nsynjs .

Se a função subjacente é promisified

o nsynjs avaliará todas as promessas sequencialmente e colocará o resultado prometido na propriedade data:

function synchronousCode() {

    var getURL = function(url) {
        return window.fetch(url).data.text().data;
    };
    
    var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
    console.log('received bytes:',getURL(url).length);
    
};

nsynjs.run(synchronousCode,{},function(){
    console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

Se a função subjacente não é promisificada

Etapa 1. Envolva a função com retorno de chamada no wrapper nsynjs-aware (se tiver uma versão promissificada, você pode pular esta etapa):

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Etapa 2. Coloque a lógica síncrona em função:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Etapa 3. Execute a função de maneira síncrona via nsynjs:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs avaliará todos os operadores e expressões passo-a-passo, pausando a execução caso o resultado de alguma função lenta não esteja pronto.

Mais exemplos aqui: https://github.com/amaksr/nsynjs/tree/master/examples

54
amaksr

É um problema muito comum que enfrentamos enquanto lutamos com os 'mistérios' do JavaScript. Deixe-me tentar desmistificar esse mistério hoje.

Vamos começar com uma função JavaScript simples:

function foo(){
// do something 
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

Essa é uma chamada de função síncrona simples (em que cada linha de código é "finalizada com seu trabalho" antes da próxima em sequência) e o resultado é o mesmo esperado.

Agora vamos adicionar um pouco de torção, introduzindo pouco atraso em nossa função, para que todas as linhas de código não sejam "finalizadas" em sequência. Assim, ele emulará o comportamento assíncrono da função:

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

Então lá vai você, esse atraso apenas quebrou a funcionalidade que esperávamos! Mas o que exatamente aconteceu? Bem, é realmente bastante lógico se você olhar o código. a função foo(), após a execução, não retorna nada (assim o valor retornado é undefined), mas inicia um timer, que executa uma função após 1s para retornar 'wohoo'. Mas como você pode ver, o valor que é atribuído à barra é o material imediatamente retornado de foo (), e nada mais que vem depois.

Então, como lidamos com essa questão?

Vamos perguntar a nossa função por um PROMISE. A promessa é realmente sobre o que isso significa: significa que a função garante que você forneça qualquer saída obtida no futuro. então vamos ver isso em ação para o nosso pequeno problema acima:

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when execution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

Assim, o resumo é - para lidar com as funções assíncronas, como chamadas baseadas em ajax, etc., você pode usar uma promessa para resolve o valor (que você pretende retornar). Assim, em suma, você resolver valor em vez de retornando, em funções assíncronas.

UPDATE (Promessas com async/await)

Além de usar then/catch para trabalhar com promessas, existe mais uma abordagem. A idéia é reconhecer uma função assíncrona e então esperar pelas promessas resolver, antes de passar para a próxima linha de código. Ainda é apenas o promises sob o capô, mas com uma abordagem sintática diferente. Para tornar as coisas mais claras, você pode encontrar uma comparação abaixo:

então/captura a versão:

function fetchUsers(){
   let users = [];
   getUsers()
   .then(_users => users = _users)
   .catch(err =>{
      throw err
   })
   return users;
 }

versão async/await:

  async function fetchUsers(){
     try{
        let users = await getUsers()
        return users;
     }
     catch(err){
        throw err;
     }
  }
42
Anish K.

O ECMAScript 6 tem 'geradores' que permitem programar facilmente em um estilo assíncrono.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Para executar o código acima, faça isso:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Se você precisa direcionar navegadores que não suportam o ES6, você pode executar o código através do Babel ou do compilador de fechamento para gerar o ECMAScript 5.

O retorno de chamada ...args é empacotado em uma matriz e desestruturado quando você os lê para que o padrão possa lidar com retornos de chamada que possuem vários argumentos. Por exemplo, com node fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
36
James

Aqui estão algumas abordagens para trabalhar com solicitações assíncronas:

  1. Objeto de promessa do navegador
  2. Q - Uma biblioteca promissora para JavaScript
  3. A + Promises.js
  4. jQuery deferred
  5. API XMLHttpRequest
  6. Usando o conceito de retorno de chamada - Como implementação na primeira resposta

Exemplo: jQuery adiou a implementação para trabalhar com várias solicitações

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.Push($.getJSON('request/ajax/url/1'));
      requests.Push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
36
Mohan Dere

Resposta curta : Seu método foo() retorna imediatamente, enquanto a chamada $ajax() executa de forma assíncrona depois que a função retorna . O problema é como ou onde armazenar os resultados recuperados pela chamada assíncrona assim que ela retornar.

Várias soluções foram dadas neste segmento. Talvez a maneira mais fácil seja passar um objeto para o método foo() e armazenar os resultados em um membro desse objeto após a conclusão da chamada assíncrona.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Observe que a chamada para foo() ainda retornará nada útil. No entanto, o resultado da chamada assíncrona agora será armazenado em result.response.

33
David R Tribble

Use uma função callback() dentro da foo() sucesso. Tente desta maneira. É simples e fácil de entender.

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
33
Mahfuzur Rahman

Nós nos encontramos em um universo que parece progredir em uma dimensão que chamamos de "tempo". Nós realmente não entendemos o que é o tempo, mas desenvolvemos abstrações e vocabulário que nos permitem raciocinar e falar sobre isso: "passado", "presente", "futuro", "antes", "depois".

Os sistemas computacionais que construímos - cada vez mais - têm o tempo como uma dimensão importante. Certas coisas estão prontas para acontecer no futuro. Então, outras coisas precisam acontecer depois que as primeiras coisas ocorrerem. Esta é a noção básica chamada "assincronia". Em nosso mundo cada vez mais conectado, o caso mais comum de assincronicidade é esperar que algum sistema remoto responda a alguma solicitação.

Considere um exemplo. Você chama o leiteiro e pede um pouco de leite. Quando chega, você quer colocá-lo em seu café. Você não pode colocar o leite no seu café agora, porque ainda não está aqui. Você tem que esperar que venha antes de colocá-lo em seu café. Em outras palavras, o seguinte não funcionará:

var milk = order_milk();
put_in_coffee(milk);

Porque JS não tem como saber que precisa wait for order_milk terminar antes de executar put_in_coffee. Em outras palavras, ele não sabe que order_milk é assíncrono - é algo que não resultará em leite até algum tempo futuro. JS e outras linguagens declarativas executam uma instrução após a outra sem esperar.

A abordagem clássica do JS a esse problema, aproveitando o fato de que o JS suporta funções como objetos de primeira classe que podem ser passados, é passar uma função como um parâmetro para a solicitação assíncrona, que será invocada quando tiver concluído sua tarefa em algum momento no futuro. Essa é a abordagem de "retorno de chamada". Se parece com isso:

order_milk(put_in_coffee);

order_milk arranca, ordena o leite e, quando e somente quando chega, invoca put_in_coffee.

O problema com essa abordagem de retorno de chamada é que ela polui a semântica normal de uma função relatando seu resultado com return; em vez disso, as funções não devem relatar seus resultados chamando um retorno de chamada fornecido como um parâmetro. Além disso, essa abordagem pode rapidamente tornar-se pesada quando se lida com sequências de eventos mais longas. Por exemplo, digamos que eu queira esperar que o leite seja colocado no café e, então, e só então, dar um terceiro passo, ou seja, beber o café. Eu acabo precisando escrever algo assim:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

onde estou passando para put_in_coffee tanto o leite para colocá-lo, e também a ação (drink_coffee) para executar uma vez que o leite tenha sido colocado. Tal código torna-se difícil de escrever, ler e depurar.

Nesse caso, poderíamos reescrever o código na pergunta como:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Digite promessas

Essa foi a motivação para a noção de uma "promessa", que é um tipo particular de valor que representa um futuro ou assíncrono resultado de algum tipo. Pode representar algo que já aconteceu, ou que vai acontecer no futuro, ou pode nunca acontecer. As promessas têm um único método, chamado then, para o qual você passa uma ação a ser executada quando o resultado que a promessa representa foi realizado.

No caso de nosso leite e café, projetamos order_milk para retornar uma promessa para o leite que chega, então especifique put_in_coffee como uma ação then, como segue:

order_milk() . then(put_in_coffee)

Uma vantagem disso é que podemos agrupá-las para criar sequências de ocorrências futuras ("encadeamento"):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Vamos aplicar promessas ao seu problema particular. Vamos envolver nossa lógica de solicitação dentro de uma função, que retorna uma promessa:

function get_data() {
  return $.ajax('/foo.json');
}

Na verdade, tudo o que fizemos foi adicionar uma return à chamada para $.ajax. Isso funciona porque o $.ajax do jQuery já retorna um tipo de coisa como promessa. (Na prática, sem entrar em detalhes, preferimos encerrar essa chamada para retornar uma promessa real ou usar alguma alternativa para $.ajax que o faça.) Agora, se quisermos carregar o arquivo e esperar que ele termine e depois fazer alguma coisa, podemos simplesmente dizer

get_data() . then(do_something)

por exemplo,

get_data() . 
  then(function(data) { console.log(data); });

Ao usar promessas, acabamos passando muitas funções para then, então é sempre útil usar as funções de seta mais compactas no estilo ES6:

get_data() . 
  then(data => console.log(data));

A palavra-chave async

Mas ainda há algo vagamente insatisfatório em ter que escrever código de uma maneira síncrona e um modo bem diferente se for assíncrono. Para síncrono, escrevemos

a();
b();

mas se a é assíncrono, com promessas, temos que escrever

a() . then(b);

Acima, dissemos: "JS não tem como saber que precisa wait para a primeira chamada terminar antes de executar a segunda". Não seria legal se houvesse era alguma maneira de dizer isso a JS? Acontece que existe - a palavra-chave await, usada dentro de um tipo especial de função chamada de função "async". Este recurso faz parte da próxima versão do ES, mas já está disponível em transpilers, como o Babel, que possui as predefinições corretas. Isso nos permite simplesmente escrever

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

No seu caso, você seria capaz de escrever algo como

async function foo() {
  data = await get_data();
  console.log(data);
}
27
user663031

É claro que existem muitas abordagens como solicitação síncrona, promessa, mas pela minha experiência eu acho que você deveria usar a abordagem de retorno de chamada. É natural o comportamento assíncrono do Javascript. Então, seu snippet de código pode ser reescrito um pouco diferente:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}
26
Khoa Bui

A questão era:

Como faço para retornar a resposta de uma chamada assíncrona?

que PODE ser interpretado como:

Como fazer assíncrono olhar de código síncrono ?

A solução será evitar callbacks e usar uma combinação de Promises e async/await .

Eu gostaria de dar um exemplo para um pedido de Ajax.

(Embora possa ser escrito em Javascript, eu prefiro escrevê-lo em Python e compilá-lo para JavaScript usando Transcrypt . Ele ficará claro o suficiente.)

Vamos primeiro ativar o uso de JQuery, para ter $ disponível como S:

__pragma__ ('alias', 'S', '$')

Defina uma função que retorne um Promise , neste caso uma chamada Ajax:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Use o assíncrono código como se fosse síncrono :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
24
Pieter Jan Bonestroo

Usando ES2017 você deve ter isso como a declaração de função

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

E executá-lo assim.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Ou a sintaxe do Promise

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})
14
Fernando Carvajal

Em vez de jogar código em você, há dois conceitos que são fundamentais para entender como o JS lida com retornos de chamada e assincronia. (Isso é mesmo uma palavra?)

o loop de eventos e o modelo de simultaneidade

Há três coisas que você precisa estar ciente; a fila; o loop de eventos e a pilha

Em termos gerais e simplistas, o loop de eventos é como o gerente de projeto, ele está constantemente ouvindo qualquer função que queira executar e se comunique entre a fila e a pilha.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Uma vez que recebe uma mensagem para executar algo, adiciona-a à fila. A fila é a lista de coisas que estão esperando para executar (como sua solicitação AJAX). imagine assim:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Quando uma dessas mensagens for executada, ela exibe a mensagem da fila e cria uma pilha, a pilha é tudo que JS precisa executar para executar a instrução na mensagem. Então, no nosso exemplo, está sendo dito para chamar foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

Portanto, qualquer coisa que o foobarFunc precise executar (no nosso caso, anotherFunction) será colocada na pilha. executado e, em seguida, esquecido - o loop de eventos passará para a próxima coisa na fila (ou ouvirá mensagens)

A principal coisa aqui é a ordem de execução. Isso é

QUANDO é algo que vai rodar

Quando você faz uma chamada usando AJAX para uma parte externa ou executa qualquer código assíncrono (um setTimeout por exemplo), o Javascript depende de uma resposta antes que ela possa continuar.

A grande questão é quando chegará a resposta? A resposta é que não sabemos - então o loop de eventos está esperando que a mensagem diga "hey run me". Se o JS apenas esperou por essa mensagem de forma síncrona, seu aplicativo congelaria e seria uma droga. Então, o JS continua executando o próximo item na fila enquanto aguarda a mensagem ser adicionada de volta à fila.

É por isso que com a funcionalidade assíncrona usamos coisas chamadas callbacks . É como um promessa literalmente. Como em I prometo retornar algo em algum momento jQuery usa callbacks específicos chamados deffered.donedeffered.fail e deffered.alwaysentre outros). Você pode ver todos eles _ (aqui

Então, o que você precisa fazer é passar uma função que é prometida para executar em algum momento com dados que são passados ​​para ele.

Como um retorno de chamada não é executado imediatamente, mas posteriormente, é importante passar a referência para a função não executada. assim

function foo(bla) {
  console.log(bla)
}

então na maior parte do tempo (mas nem sempre) você passará foo não foo()

Espero que isso faça algum sentido. Quando você encontra coisas como essas que parecem confusas - eu recomendo que você leia a documentação totalmente para, pelo menos, ter uma compreensão dela. Isso fará de você um desenvolvedor muito melhor.

13
Matthew Brent

Vamos ver a floresta primeiro antes de olhar as árvores.

Há muitas respostas informativas com grandes detalhes aqui, não vou repetir nenhuma delas. A chave para programar em JavaScript é ter primeiro o modelo mental correto da execução geral.

  1. Seu ponto de entrada é executado como resultado de um evento. Por exemplo, uma tag de script com código é carregada no navegador. (Da mesma forma, é por isso que você pode precisar se preocupar com a prontidão da página para executar seu código se ele requer que os elementos dom sejam construídos primeiro, etc.)
  2. Seu código é executado até a conclusão - não importa quantas chamadas assíncronas ele faça - sem executar any dos seus retornos de chamada, incluindo solicitações XHR, definir timeouts, manipuladores de eventos dom, etc. Cada um desses callbacks esperando para ser executado sentar em uma fila, esperando sua vez de ser executado depois que outros eventos que dispararam tenham terminado a execução.
  3. Cada retorno de chamada individual para uma solicitação XHR, definir o tempo limite ou dom o evento, uma vez invocado, será executado até a conclusão.

A boa notícia é que, se você entender bem esse ponto, nunca terá que se preocupar com as condições da corrida. Em primeiro lugar, você deve pensar em como organizar seu código como essencialmente a resposta a diferentes eventos distintos e como agrupá-los em uma sequência lógica. Você pode usar promessas ou um novo nível de async/wait como ferramentas para esse fim, ou você pode fazer o seu próprio.

Mas você não deve usar nenhuma ferramenta tática para resolver um problema até estar familiarizado com o domínio real do problema. Desenhe um mapa dessas dependências para saber o que precisa ser executado quando. Tentar uma abordagem ad-hoc para todos esses retornos de chamada simplesmente não vai atendê-lo bem.

13
Haim Zamir

Usando Promise

A resposta mais perfeita para essa pergunta é usar Promise.

function ajax(method, url, params) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open(method, url);
    xhr.send(params);
  });
}

Uso

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

Mas espere...!

Existe um problema com o uso de promessas!

Por que devemos usar nossa Promessa personalizada?

Eu estava usando esta solução por um tempo até que descobri que há um erro em navegadores antigos:

Uncaught ReferenceError: Promise is not defined

Então eu decidi implementar minha própria classe Promise para ES3 para compiladores abaixo js se não estiverem definidos. Basta adicionar este código antes de seu código principal e, em seguida, o usuário de segurança Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.Push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.Push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
12
Amir Forsati

Aqui está um exemplo que funciona:

const validateName = async userName => {
  const url = "abc/xyz";
  try {
    const response = await axios.get(url);
    return response.data
  } catch (err) {
    return false;
  }
};

validateName("user")
 .then(data => console.log(data))
 .catch(reason => console.log(reason.message))
7
Alex Montoya