example - javascript await async tutorial




Come posso restituire la risposta da una chiamata asincrona? (20)

Ho una funzione foo che fa una richiesta Ajax. Come posso restituire la risposta da parte di foo ?

Ho provato a restituire il valore dal callback di success e ad assegnare la risposta a una variabile locale all'interno della funzione e restituire quella, ma nessuno di questi modi restituisce effettivamente la risposta.

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`.

→ Per una spiegazione più generale del comportamento asincrono con diversi esempi, vedere Perché la mia variabile è inalterata dopo averla modificata all'interno di una funzione? - Riferimento di codice asincrono

→ Se hai già capito il problema, passa alle possibili soluzioni di seguito.

Il problema

L' A in Ajax sta per asynchronous . Ciò significa che l'invio della richiesta (o piuttosto la ricezione della risposta) viene portato fuori dal normale flusso di esecuzione. Nel tuo esempio, $.ajax restituisce immediatamente e l'istruzione successiva, return result; , viene eseguito prima che la funzione passata fosse chiamata anche callback di success .

Ecco un'analogia che, auspicabilmente, fa la differenza tra il sincrono e il flusso asincrono più chiaro:

Sincrono

Immagina di fare una telefonata ad un amico e chiedergli di cercare qualcosa per te. Anche se potrebbe volerci un po ', aspetti al telefono e fissi nello spazio, finché il tuo amico ti darà la risposta di cui avevi bisogno.

Lo stesso accade quando si effettua una chiamata di funzione contenente un codice "normale":

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

var item = findItem();

// Do something with item
doSomethingElse();

Anche se findItem potrebbe impiegare molto tempo per l'esecuzione, qualsiasi codice verrà dopo var item = findItem(); deve aspettare fino a quando la funzione non restituisce il risultato.

asincrono

Chiami di nuovo il tuo amico per lo stesso motivo. Ma questa volta gli dici che hai fretta e ti richiamerà sul tuo cellulare. Riattacca, esci di casa e fai qualunque cosa tu abbia pianificato di fare. Una volta che il tuo amico ti ha richiamato, hai a che fare con le informazioni che ti ha dato.

Questo è esattamente ciò che accade quando fai una richiesta Ajax.

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

Invece di attendere la risposta, l'esecuzione continua immediatamente e viene eseguita la dichiarazione dopo la chiamata Ajax. Per ottenere la risposta alla fine, si fornisce una funzione da chiamare una volta ricevuta la risposta, una richiamata (notare qualcosa? Richiamare ?). Qualsiasi istruzione che viene dopo quella chiamata viene eseguita prima che venga richiamata la richiamata.

Soluzione (s)

Abbraccia la natura asincrona di JavaScript! Mentre certe operazioni asincrone forniscono controparti sincrone (così come "Ajax"), è generalmente scoraggiato usarle, specialmente in un contesto di browser.

Perché è male chiedi?

JavaScript viene eseguito nel thread dell'interfaccia utente del browser e qualsiasi processo a esecuzione prolungata bloccherà l'interfaccia utente, rendendola non rispondente. Inoltre, esiste un limite superiore al tempo di esecuzione per JavaScript e il browser chiederà all'utente se continuare l'esecuzione o meno.

Tutto ciò è davvero una brutta esperienza utente. L'utente non sarà in grado di dire se tutto funziona correttamente o no. Inoltre, l'effetto sarà peggiore per gli utenti con una connessione lenta.

Nel seguito vedremo tre diverse soluzioni che si stanno costruendo l'una sull'altra:

  • Promesse con async/await (ES2017 +, disponibile nei browser più vecchi se si utilizza un transpiler o un rigeneratore)
  • Callback (popolare nel nodo)
  • Promesse con then() (ES2015 +, disponibile nei browser più vecchi se si utilizza una delle tante librerie promesse)

Tutti e tre sono disponibili nei browser correnti e nel nodo 7+.

ES2017 +: promette con async/await

La versione ECMAScript rilasciata nel 2017 ha introdotto il supporto a livello di sintassi per le funzioni asincrone. Con l'aiuto di async e await , è possibile scrivere in modo asincrono in uno "stile sincrono". Il codice è ancora asincrono, ma è più facile da leggere / capire.

async/await basa sulle promesse: una funzione async restituisce sempre una promessa. attendi "estrae" una promessa ed esita nel valore con cui è stata risolta la promessa o genera un errore se la promessa è stata respinta.

Importante: è possibile utilizzare solo await all'interno di una funzione async . Ciò significa che al livello più alto, devi ancora lavorare direttamente con la promessa.

Puoi leggere ulteriori informazioni su async/await e await su MDN.

Ecco un esempio che si basa sul ritardo sopra riportato:

// 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;
  }
}

// Async functions always return a promise
getAllBooks()
  .then(function(books) {
    console.log(books);
  });

Le attuali versioni di browser e node supportano async/await . Puoi anche supportare ambienti più vecchi trasformando il tuo codice in ES5 con l'aiuto di regenerator (o strumenti che usano il rigeneratore, come Babel ).

Consenti alle funzioni di accettare i callback

Un callback è semplicemente una funzione passata a un'altra funzione. Quell'altra funzione può chiamare la funzione passata ogni volta che è pronta. Nel contesto di un processo asincrono, il callback verrà chiamato ogni volta che viene eseguito il processo asincrono. Di solito, il risultato viene passato al callback.

Nell'esempio della domanda, puoi accettare di accettare una richiamata e usarla come callback di success . Così questo

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

diventa

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

Qui abbiamo definito la funzione "in linea" ma puoi passare qualsiasi riferimento di funzione:

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

foo(myCallback);

foo stesso è definito come segue:

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

callback si riferirà alla funzione che passiamo a foo quando la chiamiamo e semplicemente la passiamo al success . $.ajax dire una volta che la richiesta Ajax ha avuto successo, $.ajax chiamerà la callback e passerà la risposta al callback (che può essere riferito al result , poiché è così che abbiamo definito il callback).

È inoltre possibile elaborare la risposta prima di passarla alla richiamata:

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

È più facile scrivere codice utilizzando i callback di quanto possa sembrare. Dopotutto, JavaScript nel browser è pesantemente guidato dagli eventi (eventi DOM). Ricevere la risposta Ajax non è altro che un evento.
Potrebbero sorgere difficoltà quando si deve lavorare con il codice di terze parti, ma la maggior parte dei problemi può essere risolta semplicemente pensando al flusso dell'applicazione.

ES2015 +: promette con then()

L' then() è una nuova funzionalità di ECMAScript 6 (ES2015), ma ha già un buon supporto per i browser . Ci sono anche molte librerie che implementano l'API Promises standard e forniscono metodi aggiuntivi per facilitare l'uso e la composizione delle funzioni asincrone (es. bluebird ).

Le promesse sono contenitori per valori futuri . Quando la promessa riceve il valore (viene risolto ) o quando viene annullato ( rifiutato ), notifica a tutti i suoi "ascoltatori" che desiderano accedere a questo valore.

Il vantaggio rispetto alle callback semplici è che ti permettono di disaccoppiare il tuo codice e sono più facili da comporre.

Ecco un semplice esempio di utilizzo di una 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).
  });

Applicato alla nostra chiamata Ajax potremmo usare promesse come questa:

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
  });

Descrivere tutti i vantaggi che promettono l'offerta va oltre lo scopo di questa risposta, ma se scrivi un nuovo codice, dovresti prenderli seriamente in considerazione. Forniscono una grande astrazione e separazione del codice.

Ulteriori informazioni sulle promesse: rocce HTML5 - Promesse JavaScript

Nota a margine: oggetti differiti di jQuery

Gli oggetti posticipati sono l'implementazione personalizzata delle promesse di jQuery (prima che l'API Promise fosse standardizzata). Si comportano quasi come delle promesse ma espongono un'API leggermente diversa.

Ogni metodo Ajax di jQuery restituisce già un "oggetto posticipato" (in realtà una promessa di un oggetto posticipato) che puoi semplicemente restituire dalla tua funzione:

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

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

Nota a margine: promemoria

Tieni presente che le promesse e gli oggetti posticipati sono solo contenitori per un valore futuro, non sono il valore stesso. Ad esempio, supponi di avere il seguente:

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
}

Questo codice fraintende i suddetti problemi di asincronia. In particolare, $.ajax() non blocca il codice mentre controlla la pagina '/ password' sul server - invia una richiesta al server e mentre attende, restituisce immediatamente un oggetto rinviato Ajax jQuery, non la risposta da il server. Ciò significa che l'istruzione if sta per ottenere sempre questo oggetto rinviato, trattarlo come true e procedere come se l'utente abbia effettuato l'accesso. Non valido.

Ma la soluzione è semplice:

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
});

Non consigliato: chiamate sincrone "Ajax"

Come ho detto, alcune (!) Operazioni asincrone hanno controparti sincrone. Non sostengo il loro uso, ma per completezza, ecco come eseguire una chiamata sincrona:

Senza jQuery

Se si utilizza direttamente un oggetto XMLHTTPRequest , passare false come terzo argomento a .open .

jQuery

Se si utilizza jQuery , è possibile impostare l'opzione async su false . Si noti che questa opzione è deprecata dal jQuery 1.8. È quindi possibile continuare a utilizzare una callback o accedere alla proprietà responseText dell'oggetto jqXHR :

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

Se usi altri metodi jQuery Ajax, come $.get , $.getJSON , ecc., Devi cambiarlo in $.ajax (dato che puoi passare solo i parametri di configurazione a $.ajax ).

Dritta! Non è possibile creare una richiesta JSONP sincrona. JSONP per sua natura è sempre asincrono (un motivo in più per non considerare nemmeno questa opzione).


Js è un singolo threaded.

Il browser può essere diviso in tre parti:

1) Event Loop

2) API Web

3) Coda eventi

Event Loop viene eseguito per sempre, ovvero tipo di loop infinito. La coda di eventi è dove tutte le funzioni vengono spinte su alcuni eventi (esempio: clic) questo è uno ad uno eseguito dalla coda e inserito nel ciclo Event che esegue questa funzione e lo prepara autonomamente per il prossimo dopo che viene eseguito il primo. Ciò significa che l'esecuzione di una funzione non inizia finché la funzione non viene eseguita prima in coda nel ciclo degli eventi.

Ora pensiamo di aver spinto due funzioni in una coda per ottenere un dato dal server e un altro utilizza quei dati. Abbiamo spinto la funzione serverRequest () in coda prima poi la funzione utiliseData (). la funzione serverRequest entra nel ciclo degli eventi ed effettua una chiamata al server poiché non sappiamo mai quanto tempo ci vorrà per ottenere i dati dal server, quindi ci si aspetta che questo processo richieda del tempo e così abbiamo occupato il nostro ciclo degli eventi appeso così alla nostra pagina, che è dove Web L'API entra nel ruolo prende questa funzione dal ciclo di eventi e si occupa del server che rende libero il ciclo di eventi in modo che possiamo eseguire la prossima funzione dalla coda. La funzione successiva in coda è utiliseData () che va in loop ma a causa della mancanza di dati disponibili va lo spreco e l'esecuzione della funzione successiva continuano fino alla fine della coda. (Si chiama chiamata asincrona, ovvero possiamo fare qualcos'altro fino a quando non otteniamo i dati)

Supponiamo che la nostra funzione serverRequest () abbia un'istruzione return in un codice, quando recuperiamo i dati dal server Web API lo inseriremo in coda alla fine della coda. Non appena non viene utilizzata la sua coda, non è possibile utilizzare i suoi dati poiché non vengono utilizzate le funzioni nella coda per utilizzare questi dati. Pertanto non è possibile restituire qualcosa da Async Call.

Quindi la soluzione a questo è callback o promessa .

Un'immagine da una delle risposte qui, spiega correttamente l'uso del callback ... Diamo alla nostra funzione (funzione che utilizza i dati restituiti dal server) il funzionamento del server di chiamata.

 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);

}

Nel mio codice è chiamato come

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");
}

Leggi qui per i nuovi metodi in ECMA (2016/17) per effettuare una chiamata asincrona (@Felix Kling Rispondi in cima) https://.com/a/14220323/7579856


Se usi le promesse, questa risposta è per te.

Ciò significa AngularJS, jQuery (con differimento), sostituzione XHR nativa (recupero), EmberJS, salvataggio di BackboneJS o qualsiasi libreria di nodi che restituisce promesse.

Il tuo codice dovrebbe essere qualcosa del genere:

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 ha fatto un ottimo lavoro scrivendo una risposta per le persone che usano jQuery con i callback per AJAX. Ho una risposta per XHR nativo. Questa risposta è per l'uso generico di promesse sul frontend o back-end.

Il problema principale

Il modello di concorrenza JavaScript nel browser e sul server con NodeJS / io.js è asincrono e reattivo .

Ogni volta che chiami un metodo che restituisce una promessa, i gestori then vengono sempre eseguiti in modo asincrono, cioè dopo il codice sottostante che non si trova in un gestore .then .

Ciò significa che quando restituisci i data il gestore che hai definito non è ancora stato eseguito. Ciò a sua volta significa che il valore che stai restituendo non è stato impostato sul valore corretto nel tempo.

Ecco una semplice analogia per il 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

Il valore dei data undefined è undefined poiché la parte data = 5 non è ancora stata eseguita. Probabilmente verrà eseguito in un secondo, ma a quel punto è irrilevante per il valore restituito.

Dato che l'operazione non è ancora avvenuta (AJAX, chiamata server, IO, timer) si restituisce il valore prima che la richiesta abbia la possibilità di dire al proprio codice quale sia quel valore.

Una possibile soluzione a questo problema è codificare in modo reattivo , dicendo al tuo programma cosa fare quando il calcolo sarà completato. Le promesse lo rendono attivo attivamente essendo di natura temporale (sensibile al tempo).

Ricapitolazione rapida sulle promesse

Una promessa è un valore nel tempo . Le promesse hanno lo stato, iniziano come pendenti senza valore e possono accontentarsi di:

  • significato compiuto che il calcolo è stato completato con successo.
  • significato rifiutato che il calcolo non è riuscito.

Una promessa può cambiare stato solo una volta dopo il quale rimarrà sempre nello stesso stato per sempre. È possibile associare then gestori alle promesse per estrarne il valore e gestire gli errori. then gestori consentono il chaining delle chiamate. Le promesse vengono create utilizzando le API che le restituiscono . Ad esempio, il più moderno rimedio di sostituzione AJAX o le promesse di ritorno $.get jQuery.

Quando chiamiamo, .then , una promessa e ne restituiamo qualcosa, otteniamo una promessa per il valore elaborato . Se restituiremo un'altra promessa otterremo cose incredibili, ma teniamo i nostri cavalli.

Con promesse

Vediamo come possiamo risolvere il problema sopra con promesse. Per prima cosa, dimostriamo la nostra comprensione degli stati di promessa dall'alto utilizzando il costruttore Promise per creare una funzione di ritardo:

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);
    });
}

Ora, dopo aver convertito setTimeout per usare le promesse, possiamo usarlo per farlo contare:

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;
});

Fondamentalmente, invece di restituire un valore che non possiamo fare a causa del modello di concorrenza, stiamo restituendo un wrapper per un valore che possiamo scartare con then. È come una scatola con cui puoi aprire then.

Applicando questo

Questo è lo stesso per la tua chiamata API originale, puoi:

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`
})

Quindi funziona altrettanto bene. Abbiamo appreso che non possiamo restituire valori da chiamate già asincrone, ma possiamo usare le promesse e concatenarle per eseguire l'elaborazione. Ora sappiamo come restituire la risposta da una chiamata asincrona.

ES2015 (ES6)

ES6 introduce i generators che sono funzioni che possono tornare nel mezzo e quindi riprendere il punto in cui erano. Questo è in genere utile per le sequenze, ad esempio:

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

È una funzione che restituisce un iteratore sulla sequenza 1,2,3,3,3,3,....che può essere iterata. Anche se questo è interessante da solo e apre spazio per molte possibilità, c'è un caso particolare interessante.

Se la sequenza che stiamo producendo è una sequenza di azioni piuttosto che numeri - possiamo sospendere la funzione ogni volta che un'azione viene resa e attendere prima che riprendiamo la funzione. Quindi, invece di una sequenza di numeri, abbiamo bisogno di una sequenza di valori futuri , ovvero: promesse.

Questo trucco un po 'complicato ma molto potente ci consente di scrivere codice asincrono in modo sincrono. Ci sono diversi "corridori" che fanno questo per te, scrivendo uno è un breve poche righe di codice ma è oltre lo scopo di questa risposta. Userò Bluebird Promise.coroutinequi, ma ci sono altri wrapper come coo 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
});

Questo metodo restituisce una promessa in sé, che possiamo consumare da altre coroutine. Per esempio:

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)

In ES7, questo è ulteriormente standardizzato, ci sono diverse proposte in questo momento, ma in tutti voi potete awaitprometterlo. Questo è solo "zucchero" (sintassi più gradevole) per la proposta ES6 di cui sopra aggiungendo le parole chiave asynce await. Fare l'esempio sopra:

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
}

Ritorna comunque una promessa lo stesso :)


Risposta 2017: ora puoi fare esattamente ciò che desideri in ogni browser e nodo corrente

Questo è abbastanza semplice:

  • Restituire una promessa
  • Usa l' await , che dirà a JavaScript di attendere la promessa di essere risolta in un valore (come la risposta HTTP)
  • Aggiungi la parola chiave async/await alla funzione genitore

Ecco una versione funzionante del tuo codice:

(async function(){

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

})()

Attendere è supportato in tutti i browser correnti e nel nodo 8


Ecco alcuni approcci per lavorare con le richieste asincrone:

  1. then()
  2. Q - Una libreria di promessa per JavaScript
  3. A + Promises.js
  4. jQuery differito
  5. API XMLHttpRequest
  6. Utilizzo del concetto di callback - Come implementazione nella prima risposta

Esempio: implementazione differita di jQuery per lavorare con più richieste

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();


È possibile utilizzare questa libreria personalizzata (scritta utilizzando Promise) per effettuare una chiamata 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);
        };
    });
}

Semplice esempio di utilizzo:

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

Dai un'occhiata a questo esempio:

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);
    });
});

Come puoi vedere getJokesta restituendo una promessa risolta (è risolta al suo ritorno res.data.value). Quindi attendi che la richiesta $ http.get sia completata e quindi console.log (res.joke) viene eseguito (come un normale flusso asincrono).

Questo è il plnkr:

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


Risposta breve : il foo()metodo restituisce immediatamente, mentre la $ajax()chiamata viene eseguita in modo asincrono dopo il ritorno della funzione . Il problema è quindi come o dove memorizzare i risultati recuperati dalla chiamata asincrona una volta che ritorna.

Diverse soluzioni sono state fornite in questo thread. Forse il modo più semplice è passare un oggetto al foo()metodo e archiviare i risultati in un membro di quell'oggetto al termine della chiamata asincrona.

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

Si noti che la chiamata a foo()non restituirà ancora nulla di utile. Tuttavia, il risultato della chiamata asincrona verrà ora archiviato result.response.


ECMAScript 6 ha 'generatori' che ti permettono di programmare facilmente in uno stile asincrono.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://.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();
    }
}

Per eseguire il codice sopra, fai questo:

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

Se devi scegliere come target i browser che non supportano ES6, puoi eseguire il codice tramite Babel o closure-compiler per generare ECMAScript 5.

Il callback ...argsviene avvolto in una matrice e destrutturato quando vengono letti in modo che il pattern possa far fronte a callback con più argomenti. Ad esempio con nodo fs :

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

Il seguente esempio che ho scritto mostra come

  • Gestire le chiamate HTTP asincrone;
  • Attendi la risposta da ciascuna chiamata API;
  • Usa modello Promise ;
  • Usa Promise.Tutti i pattern per unire più chiamate HTTP;

Questo esempio di lavoro è autonomo. Definirà un oggetto di richiesta semplice che utilizza l' XMLHttpRequestoggetto finestra per effettuare chiamate. Definirà una semplice funzione per attendere il completamento di una serie di promesse.

Contesto. L'esempio sta interrogando l' endpoint dell'API Spotify Web per cercare playlistoggetti per un determinato set di stringhe di query:

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

Per ogni articolo, una nuova Promessa genererà un blocco ExecutionBlock, analizzerà il risultato, pianificherà una nuova serie di promesse basate sull'array dei risultati, ovvero un elenco di useroggetti Spotify ed eseguirà la nuova chiamata HTTP all'interno in ExecutionProfileBlockmodo asincrono.

È quindi possibile visualizzare una struttura Promise nidificata, che consente di generare più chiamate HTTP nidificate completamente asincrone e unire i risultati di ogni sottoinsieme di chiamate Promise.all.

NOTA Le recenti searchAPI Spotify richiederanno un token di accesso da specificare nelle intestazioni delle richieste:

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

Pertanto, per eseguire l'esempio seguente è necessario inserire il token di accesso nelle intestazioni della richiesta:

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" />

Ho discusso ampiamente di questa soluzione here .


Mentre promesse e callback funzionano bene in molte situazioni, è un dolore nella parte posteriore esprimere qualcosa come:

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

Finiresti per passare attraverso async1; controllare se nameè indefinito o meno e chiamare di conseguenza la richiamata.

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

async1(name, async2)

Mentre va bene in piccoli esempi diventa fastidioso quando si hanno molti casi simili e la gestione degli errori coinvolti.

Fibers aiuta a risolvere il 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
}

Puoi controllare il progetto here .


Un altro approccio per restituire un valore da una funzione asincrona consiste nel passare in un oggetto che memorizzerà il risultato dalla funzione asincrona.

Ecco un esempio dello stesso:

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');
});

Sto usando l' resultoggetto per memorizzare il valore durante l'operazione asincrona. Ciò consente al risultato di essere disponibile anche dopo il lavoro asincrono.

Io uso questo approccio molto. Sarei interessato a sapere quanto bene questo approccio funziona dove è coinvolto il cablaggio del risultato attraverso moduli consecutivi.


È un problema molto comune che affrontiamo mentre lottiamo con i "misteri" di JavaScript. Lasciatemi provare a demistificare questo mistero oggi.

Iniziamo con una semplice funzione JavaScript:

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

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

Questa è una semplice chiamata di funzione sincrona (dove ogni riga di codice è 'completata con il suo lavoro' prima di quella successiva in sequenza) e il risultato è lo stesso di quanto previsto.

Ora aggiungiamo un po 'di twist, introducendo un piccolo ritardo nella nostra funzione, in modo che tutte le linee di codice non siano "finite" in sequenza. Quindi, emulerà il comportamento asincrono della funzione:

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

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

Quindi ecco, questo ritardo ha appena rotto la funzionalità che ci aspettavamo! Ma cosa è successo esattamente? Beh, in realtà è piuttosto logico se si guarda il codice. la funzione foo(), al momento dell'esecuzione, non restituisce nulla (quindi il valore restituito è undefined), ma avvia un timer, che esegue una funzione dopo 1s per restituire 'wohoo'. Ma come puoi vedere, il valore che viene assegnato alla barra è la roba immediatamente restituita da foo (), non qualsiasi altra cosa che viene dopo.

Quindi, come affrontiamo questo problema?

Chiediamo la nostra funzione per una PROMESSA . La promessa riguarda davvero cosa significa: significa che la funzione ti garantisce di fornire qualsiasi risultato che otterrà in futuro. quindi vediamo in azione per il nostro piccolo problema sopra:

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'
});

Quindi, il sommario è - per affrontare le funzioni asincrone come le chiamate basate su un jax, ecc., Puoi usare una promessa al resolvevalore (che intendi restituire). Quindi, in breve, risolvi il valore invece di tornare , in funzioni asincrone.

AGGIORNAMENTO (promette con async / await)

Oltre all'utilizzo then/catchper lavorare con le promesse, esiste un altro approccio. L'idea è di riconoscere una funzione asincrona e quindi attendere che le promesse si risolvano, prima di passare alla riga successiva del codice. È ancora solo promisessotto il cofano, ma con un diverso approccio sintattico. Per rendere le cose più chiare, puoi trovare un confronto di seguito:

quindi / cattura versione:

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

versione asincrona / attesa:

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

Ci troviamo in un universo che sembra progredire lungo una dimensione che chiamiamo "tempo". Non capiamo veramente che ore siano, ma abbiamo sviluppato astrazioni e vocaboli che ci permettono di ragionare e parlarne: "passato", "presente", "futuro", "prima", "dopo".

I sistemi informatici che costruiamo - sempre di più - hanno il tempo come una dimensione importante. Certe cose sono programmate per accadere in futuro. Poi altre cose devono accadere dopo che queste prime cose si sono verificate. Questa è la nozione base chiamata "asincronicità". Nel nostro mondo sempre più collegato in rete, il caso più comune di asinconicità è aspettare che qualche sistema remoto risponda ad alcune richieste.

Considera un esempio. Chiami il lattaio e ordina del latte. Quando arriva, vuoi metterlo nel tuo caffè. Non puoi mettere il latte nel tuo caffè adesso, perché non è ancora qui. Devi aspettare che arrivi prima di metterlo nel tuo caffè. In altre parole, il seguente non funzionerà:

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

Perché JS non ha modo di sapere che deve aspettare per order_milkfinire prima che venga eseguito put_in_coffee. In altre parole, non sa che order_milkè asincrono - è qualcosa che non porterà al latte fino a qualche tempo futuro. JS e altri linguaggi dichiarativi eseguono una dichiarazione dopo l'altra senza attendere.

Il classico approccio JS a questo problema, sfruttando il fatto che JS supporta le funzioni come oggetti di prima classe che possono essere passati, è passare una funzione come parametro alla richiesta asincrona, che verrà poi richiamata quando è completa il suo compito a volte in futuro. Questo è l'approccio "callback". Sembra questo:

order_milk(put_in_coffee);

order_milkprende il via, ordina il latte, quindi, quando e solo quando arriva, invoca put_in_coffee.

Il problema con questo approccio callback è che inquina la normale semantica di una funzione che riporta il suo risultato con return; invece, le funzioni devono nn notificare i risultati chiamando una richiamata fornita come parametro. Inoltre, questo approccio può diventare rapidamente ingombrante quando si ha a che fare con sequenze di eventi più lunghe. Per esempio, diciamo che voglio aspettare che il latte sia messo nel caffè, e poi, solo dopo, eseguire un terzo passaggio, cioè bere il caffè. Alla fine ho bisogno di scrivere qualcosa come questo:

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

dove sto passando ad put_in_coffeeentrambi il latte da mettere dentro, e anche l'action ( drink_coffee) da eseguire una volta che il latte è stato inserito. Tale codice diventa difficile da scrivere, leggere e fare il debug.

In questo caso, potremmo riscrivere il codice nella domanda come:

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

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

Inserisci promesse

Questa era la motivazione per la nozione di una "promessa", che è un particolare tipo di valore che rappresenta un risultato futuro o asincrono di qualche tipo. Può rappresentare qualcosa che è già accaduto, o che succederà in futuro, o potrebbe non accadere mai. Le promesse hanno un unico metodo, denominato then, al quale si passa un'azione da eseguire quando il risultato che la promessa rappresenta è stato realizzato.

Nel caso del nostro latte e caffè, progettiamo order_milkdi restituire una promessa per il latte in arrivo, quindi specificare put_in_coffeecome thenazione, come segue:

order_milk() . then(put_in_coffee)

Un vantaggio di questo è che possiamo metterli insieme per creare sequenze di occorrenze future ("concatenazione"):

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

Applichiamo le promesse al tuo particolare problema. Trasformeremo la nostra logica di richiesta all'interno di una funzione, che restituisce una promessa:

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

In realtà, tutto ciò che abbiamo fatto è stato aggiunto returnalla chiamata a $.ajax. Questo funziona perché jQuery $.ajaxrestituisce già una sorta di cosa promettente. (In pratica, senza entrare nei dettagli, preferiremmo avvolgere questa chiamata in modo da restituire una vera promessa, o usare un'alternativa a $.ajaxciò.) Ora, se vogliamo caricare il file e aspettare che finisca e poi fare qualcosa, possiamo semplicemente dire

get_data() . then(do_something)

per esempio,

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

Quando utilizziamo le promesse, finiamo per passare molte funzioni then, quindi è spesso utile utilizzare le funzioni di freccia in stile ES6 più compatte:

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

La asyncparola chiave

Ma c'è ancora qualcosa di vagamente insoddisfacente nel dover scrivere codice in un modo se sincrono e in un modo completamente diverso se asincrono. Per sincrono, scriviamo

a();
b();

ma se aè asincrono, con le promesse dobbiamo scrivere

a() . then(b);

Sopra, abbiamo detto "JS non ha modo di sapere che deve aspettare che la prima chiamata finisca prima di eseguire il secondo". Non sarebbe bello se ci fosse un modo per dirlo a JS? Si scopre che c'è - la awaitparola chiave, usata all'interno di un tipo speciale di funzione chiamata funzione "asincrona". Questa funzione fa parte della versione imminente di ES, ma è già disponibile in transpilers come Babel, dato i preset giusti. Questo ci permette semplicemente di scrivere

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

Nel tuo caso, saresti in grado di scrivere qualcosa del genere

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

La soluzione più semplice è creare una funzione JavaScript e chiamarla per il successcallback Ajax .

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);    
}); 

Naturalmente ci sono molti approcci come la richiesta sincrona, la promessa, ma dalla mia esperienza penso che dovresti usare l'approccio callback. È naturale il comportamento asincrono di Javascript. Pertanto, lo snippet di codice può essere riscritto in modo leggermente diverso:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}

Questo è uno dei luoghi in cui il bind dei dati in due modi utilizzato in molti nuovi framework JavaScript funzionerà molto per te ...

Quindi, se usi Angular, React o qualsiasi altro framework che esegue il binding dei dati in due modi, questo problema è semplicemente corretto per te, quindi in parole semplici, il tuo risultato è undefinednella prima fase, così hai ottenuto result = undefinedprima di ricevere i dati, quindi non appena ottieni il risultato, viene aggiornato e viene assegnato al nuovo valore che risponde alla tua chiamata Ajax ...

Ma come puoi farlo in puro javascript o jQuery, ad esempio, come hai chiesto in questa domanda?

Puoi usare un callback , promettere e recentemente osservabile per gestirlo per te, per esempio nelle promesse abbiamo alcune funzioni come success () o then () che saranno eseguite quando i tuoi dati sono pronti per te, lo stesso con la funzione callback o subscribe su osservabile .

Ad esempio nel tuo caso che stai usando jQuery , puoi fare qualcosa del genere:

$(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
});

Per ulteriori informazioni, studia le promesse e le osservabili che sono i nuovi modi per fare questo async.


Risponderò con un orribile fumetto disegnato a mano. La seconda immagine è la ragione per cui resultè undefinednel tuo esempio di codice.


Stai utilizzando Ajax in modo errato. L'idea è di non restituire nulla, ma invece di trasferire i dati a qualcosa chiamata funzione di callback, che gestisce i dati.

Questo è:

function handleData( responseData ) {

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

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

Restituire qualcosa nel gestore di invio non farà nulla. Devi invece consegnare i dati o fare ciò che vuoi direttamente all'interno della funzione di successo.


Un'altra soluzione è eseguire codice tramite sequencer executor nsynjs .

Se la funzione sottostante è promessa

nsynjs valuterà tutte le promesse in sequenza e metterà i risultati di promessa in dataproprietà:

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 la funzione sottostante non è promessa

Passaggio 1. Funzione di avvolgimento con richiamata nel wrapper nsynjs-aware (se ha una versione promessa, è possibile saltare questo test):

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;

Passaggio 2. Mettere in funzione la logica sincrona:

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

Passaggio 3. Eseguire la funzione in modo sincrono tramite nnsynjs:

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

Nsynjs valuterà passo dopo passo tutti gli operatori e le espressioni, interrompendo l'esecuzione nel caso in cui il risultato di qualche funzione lenta non sia pronto.

Altri esempi qui: https://github.com/amaksr/nsynjs/tree/master/examples





ecmascript-2017