Come restituisco la risposta da una chiamata asincrona?

Jan 09 2013

Ho una funzione fooche fa una richiesta asincrona. Come posso restituire la risposta / risultato da foo?

Ho provato a restituire il valore dal callback, oltre ad assegnare il risultato a una variabile locale all'interno della funzione e restituire quella, ma nessuno di questi modi restituisce effettivamente la risposta (restituiscono tutti undefinedo qualunque sia il valore iniziale della variabile result) .

Esempio utilizzando la ajaxfunzione di jQuery :

function foo() {
    var result;

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

    return result; // It always returns `undefined`
}

Esempio utilizzando node.js:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

Esempio utilizzando il thenblocco di una promessa:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

Risposte

5905 FelixKling Jan 09 2013 at 00:06

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

→ Se si comprende già il problema, passare alle possibili soluzioni di seguito.

Il problema

La A in Ajax sta per asincrono . Ciò significa che l'invio della richiesta (o meglio la ricezione della risposta) viene escluso dal normale flusso di esecuzione. Nel tuo esempio, $.ajaxritorna immediatamente e l'istruzione successiva,, return result;viene eseguita prima ancora che la funzione che hai passato come successcallback fosse chiamata.

Ecco un'analogia che si spera renda più chiara la differenza tra flusso sincrono e asincrono:

Sincrono

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

Anche se l' findItemesecuzione potrebbe richiedere molto tempo, qualsiasi codice successivo var item = findItem();deve attendere fino a quando la funzione restituisce il risultato.

Asincrono

Chiami di nuovo il tuo amico per lo stesso motivo. Ma questa volta gli dici che hai fretta e che dovrebbe richiamarti sul tuo cellulare. Riattacchi, esci di casa e fai tutto ciò che avevi programmato di fare. Quando il tuo amico ti richiama, 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 the item
});
doSomethingElse();

Invece di attendere la risposta, l'esecuzione continua immediatamente e l'istruzione dopo la chiamata Ajax viene eseguita. Per ottenere la risposta alla fine, fornisci una funzione da chiamare una volta ricevuta la risposta, una richiamata (nota qualcosa? Richiama ?). Qualsiasi istruzione successiva a quella chiamata viene eseguita prima che venga chiamata la richiamata.


Soluzione / i

Abbraccia la natura asincrona di JavaScript! Sebbene alcune operazioni asincrone forniscano controparti sincrone (così fa "Ajax"), è generalmente sconsigliato utilizzarle, specialmente in un contesto di browser.

Perché è brutto lo chiedi?

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

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

Di seguito vedremo tre diverse soluzioni che si stanno costruendo una sopra l'altra:

  • async/awaitPromette con (ES2017 +, disponibile nei browser meno recenti se si utilizza un transpiler o un rigeneratore)
  • Callback (popolari nel nodo)
  • Promesse conthen() (ES2015 +, disponibile nei browser meno recenti se si utilizza una delle tante librerie di promesse)

Tutti e tre sono disponibili nei browser attuali 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 asynce await, puoi scrivere in modo asincrono in uno "stile sincrono". Il codice è ancora asincrono, ma è più facile da leggere / comprendere.

async/awaitsi basa sulle promesse: una asyncfunzione restituisce sempre una promessa. await"scarta" una promessa e restituisce il valore con cui la promessa è stata risolta o genera un errore se la promessa è stata rifiutata.

Importante: puoi usare solo awaitall'interno di una asyncfunzione. Al momento, il livello superiore awaitnon è ancora supportato, quindi potrebbe essere necessario creare un IIFE ( Espressione di funzione immediatamente invocata) asincrono per avviare un asynccontesto.

Puoi leggere di più su asynce awaitsu MDN.

Ecco un esempio che si basa sul ritardo sopra:

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

Supporto delle versioni correnti del browser e del nodoasync/await . Puoi anche supportare ambienti più vecchi trasformando il tuo codice in ES5 con l'aiuto del rigeneratore (o strumenti che utilizzano il rigeneratore, come Babel ).


Consentire alle funzioni di accettare i callback

Un callback è quando la funzione 1 viene passata alla funzione 2. La funzione 2 può chiamare la funzione 1 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 fare in modo che fooaccetti una richiamata e successusala come richiamata. Così questo

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

diventa

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

Qui abbiamo definito la funzione "inline" ma puoi passare qualsiasi riferimento alla funzione:

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

foo(myCallback);

foo stesso è definito come segue:

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

callbackfarà riferimento alla funzione a cui passiamo fooquando la chiamiamo e la passiamo success. Cioè una volta che la richiesta Ajax ha successo, $.ajaxchiamerà callbacke passerà la risposta al callback (a cui ci si può riferire result, poiché è così che abbiamo definito il callback).

Puoi anche 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 usando i callback di quanto possa sembrare. Dopo tutto, JavaScript nel browser è fortemente guidato dagli eventi (eventi DOM). La ricezione della risposta Ajax non è altro che un evento.
Potrebbero sorgere difficoltà quando devi lavorare con codice di terze parti, ma la maggior parte dei problemi può essere risolta semplicemente pensando al flusso dell'applicazione.


ES2015 +: promette con then ()

L' API Promise è 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 di funzioni asincrone (es. Bluebird ).

Le promesse sono contenitori di valori futuri . Quando la promessa riceve il valore (viene risolta ) o quando viene annullata ( rifiutata ), avvisa tutti i suoi "ascoltatori" che vogliono accedere a questo valore.

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

Ecco un 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 la promessa offre va oltre lo scopo di questa risposta, ma se scrivi un nuovo codice, dovresti considerarli seriamente. Forniscono una grande astrazione e separazione del codice.

Ulteriori informazioni sulle promesse: HTML5 Rocks - JavaScript Promises

Nota a margine: gli oggetti differiti di jQuery

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

Ogni metodo Ajax di jQuery restituisce già un "oggetto differito" (in realtà una promessa di un oggetto differito) 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: prometti trucchi

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

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 problemi di asincronia di cui sopra. In particolare, $.ajax()non congela il codice mentre controlla la pagina '/ password' sul tuo server: invia una richiesta al server e mentre attende, restituisce immediatamente un oggetto jQuery Ajax Deferred, non la risposta dal server. Ciò significa che l' ifistruzione riceverà sempre questo oggetto Deferred, lo tratterà come truee procederà come se l'utente fosse connesso. Non va bene.

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 "Ajax" sincrone

Come ho già detto, alcune (!) Operazioni asincrone hanno controparti sincrone. Non ne sostengo l'uso, ma per completezza, ecco come eseguiresti una chiamata sincrona:

Senza jQuery

Se usi direttamente un XMLHttpRequestoggetto, passa falsecome terzo argomento a .open.

jQuery

Se usi jQuery , puoi impostare l' asyncopzione su false. Nota che questa opzione è deprecata da jQuery 1.8. È quindi possibile utilizzare ancora una successrichiamata o accedere alla responseTextproprietà dell'oggetto jqXHR :

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

Se si utilizza qualsiasi altro metodo di jQuery Ajax, come ad esempio $.get, $.getJSONe così via, è necessario cambiare a $.ajax(dato che è possibile passare solo i parametri di configurazione a $.ajax).

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

1096 BenjaminGruenbaum May 30 2013 at 06:30

Se stai non utilizzando jQuery nel codice, questa risposta è per voi

Il tuo codice dovrebbe essere qualcosa sulla falsariga di questo:

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 ha fatto un ottimo lavoro scrivendo una risposta per le persone che usano jQuery per AJAX, ho deciso di fornire un'alternativa per le persone che non lo sono.

( Nota, per coloro che utilizzano la nuova fetchAPI, Angular o promesse ho aggiunto un'altra risposta di seguito )


Quello che stai affrontando

Questo è un breve riassunto della "Spiegazione del problema" dall'altra risposta, se non sei sicuro dopo aver letto questo, leggi quello.

La A in AJAX sta per asincrono . Ciò significa che l'invio della richiesta (o meglio la ricezione della risposta) viene escluso dal normale flusso di esecuzione. Nel tuo esempio, .sendritorna immediatamente e l'istruzione successiva,, return result;viene eseguita prima ancora che la funzione che hai passato come successcallback fosse chiamata.

Ciò significa che quando torni, il listener che hai definito non è stato ancora eseguito, il che significa che il valore che stai restituendo non è stato definito.

Ecco una semplice analogia

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

(Violino)

Il valore di arestituito è undefinedpoiché la a=5parte non è stata ancora eseguita. AJAX si comporta in questo modo, stai restituendo il valore prima che il server abbia la possibilità di dire al tuo browser qual è quel valore.

Una possibile soluzione a questo problema è codificare in modo reattivo , dicendo al programma cosa fare una volta completato il calcolo.

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

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

Questo si chiama CPS . Fondamentalmente, stiamo passando getFiveun'azione da eseguire quando viene completata, stiamo dicendo al nostro codice come reagire quando un evento viene completato (come la nostra chiamata AJAX, o in questo caso il timeout).

L'utilizzo sarebbe:

getFive(onComplete);

Che dovrebbe avvisare "5" sullo schermo. (Violino) .

Possibili soluzioni

Esistono fondamentalmente due modi per risolvere questo problema:

  1. Rendi sincrona la chiamata AJAX (chiamiamola SJAX).
  2. Ristrutturare il codice per funzionare correttamente con i callback.

1. AJAX sincrono - Non farlo !!

Per quanto riguarda AJAX sincrono, non farlo! La risposta di Felix solleva alcuni argomenti convincenti sul motivo per cui è una cattiva idea. Per riassumere, bloccherà il browser dell'utente fino a quando il server non restituirà la risposta e creerà un'esperienza utente molto negativa. Ecco un altro breve riassunto tratto da MDN sul perché:

XMLHttpRequest supporta comunicazioni sincrone e asincrone. In generale, tuttavia, le richieste asincrone dovrebbero essere preferite alle richieste sincrone per motivi di prestazioni.

In breve, le richieste sincrone bloccano l'esecuzione del codice ... ... questo può causare seri problemi ...

Se si dispone di farlo, è possibile passare una bandiera: Ecco come:

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. Codice di ristrutturazione

Lascia che la tua funzione accetti una richiamata. Nell'esempio è foopossibile fare in modo che il codice accetti una richiamata. Diremo al nostro codice come reagire una volta foocompletato.

Così:

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

Diventa:

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

Qui abbiamo passato una funzione anonima, ma potremmo altrettanto facilmente passare un riferimento a una funzione esistente, facendola sembrare:

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

Per maggiori dettagli su come viene fatto questo tipo di design di callback, controlla la risposta di Felix.

Ora, definiamo foo stesso per agire di conseguenza

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)

Ora abbiamo fatto in modo che la nostra funzione foo accetti un'azione da eseguire quando AJAX viene completato con successo, possiamo estenderlo ulteriormente controllando se lo stato della risposta non è 200 e agendo di conseguenza (creare un gestore di errori e simili). Risolvendo efficacemente il nostro problema.

Se hai ancora difficoltà a capirlo, leggi la guida introduttiva di AJAX su MDN.

411 cocco Aug 19 2013 at 15:06

XMLHttpRequest 2 (prima di tutto leggi le risposte di Benjamin Gruenbaum e Felix Kling )

Se non usi jQuery e desideri un breve XMLHttpRequest 2 che funzioni sui browser moderni e anche sui browser mobili ti consiglio di usarlo in questo modo:

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

Come potete vedere:

  1. È più breve di tutte le altre funzioni elencate.
  2. La richiamata viene impostata direttamente (quindi nessuna chiusura extra non necessaria).
  3. Usa il nuovo onload (quindi non devi controllare lo stato readystate e lo stato)
  4. Ci sono altre situazioni che non ricordo che rendono fastidioso XMLHttpRequest 1.

Esistono due modi per ottenere la risposta di questa chiamata Ajax (tre utilizzando il nome var XMLHttpRequest):

Il più semplice:

this.response

O se per qualche motivo bind()richiami una classe:

e.target.response

Esempio:

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

Oppure (quello sopra è meglio le funzioni anonime sono sempre un problema):

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

Niente di più facile.

Ora alcune persone probabilmente diranno che è meglio usare onreadystatechange o anche il nome della variabile XMLHttpRequest. È sbagliato.

Scopri le funzionalità avanzate di XMLHttpRequest

Supportava tutti i * browser moderni. E posso confermare che sto usando questo approccio poiché esiste XMLHttpRequest 2. Non ho mai avuto alcun tipo di problema su tutti i browser che utilizzo.

onreadystatechange è utile solo se vuoi ottenere le intestazioni sullo stato 2.

L'uso del XMLHttpRequestnome della variabile è un altro grosso errore in quanto è necessario eseguire il callback all'interno delle chiusure onload / oreadystatechange altrimenti l'hai perso.


Ora se vuoi qualcosa di più complesso usando post e FormData puoi facilmente estendere questa funzione:

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

Ancora una volta ... è una funzione molto breve, ma riceve e invia.

Esempi di utilizzo:

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

Oppure passa un elemento completo del modulo ( document.getElementsByTagName('form')[0]):

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

Oppure imposta alcuni valori personalizzati:

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

Come puoi vedere non ho implementato la sincronizzazione ... è una brutta cosa.

Detto questo ... perché non farlo nel modo più semplice?


Come accennato nel commento, l'uso di errore && sincrono interrompe completamente il punto della risposta. Qual è un bel modo breve per utilizzare Ajax nel modo corretto?

Gestore degli errori

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

Nello script precedente, hai un gestore degli errori che è definito staticamente in modo da non compromettere la funzione. Il gestore degli errori può essere utilizzato anche per altre funzioni.

Ma per ottenere davvero un errore l' unico modo è scrivere un URL sbagliato, nel qual caso ogni browser genera un errore.

I gestori di errori sono forse utili se si impostano intestazioni personalizzate, si imposta responseType sul buffer dell'array BLOB o qualsiasi altra cosa ...

Anche se passi "POSTAPAPAP" come metodo, non verrà generato un errore.

Anche se passi "fdggdgilfdghfldj" come formdata, non verrà generato un errore.

Nel primo caso l'errore è all'interno del displayAjax()sotto this.statusTextas Method not Allowed.

Nel secondo caso, funziona semplicemente. Devi controllare sul lato server se hai passato i dati corretti del post.

interdominio non consentito genera automaticamente un errore.

Nella risposta all'errore non sono presenti codici di errore.

C'è solo il this.typeche è impostato su errore.

Perché aggiungere un gestore degli errori se non hai il controllo totale sugli errori? La maggior parte degli errori vengono restituiti all'interno di questo nella funzione di callback displayAjax().

Quindi: non sono necessari controlli di errore se sei in grado di copiare e incollare correttamente l'URL. ;)

PS: Come primo test ho scritto x ('x', displayAjax) ..., e ha ottenuto totalmente una risposta ... ??? Quindi ho controllato la cartella in cui si trova l'HTML e c'era un file chiamato "x.xml". Quindi, anche se dimentichi l'estensione del tuo file XMLHttpRequest 2, LO TROVERAI . Mi sono divertito


Leggi un file sincrono

Non farlo.

Se vuoi bloccare il browser per un po 'carica un bel .txtfile grande sincrono.

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

Adesso puoi farlo

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

Non c'è altro modo per farlo in modo non asincrono. (Sì, con setTimeout loop ... ma sul serio?)

Un altro punto è ... se lavori con le API o solo con i file della tua lista o qualsiasi altra cosa, usi sempre funzioni diverse per ogni richiesta ...

Solo se hai una pagina dove carichi sempre lo stesso XML / JSON o qualsiasi altra cosa ti serve una sola funzione. In tal caso, modifica un po 'la funzione Ajax e sostituisci b con la tua funzione speciale.


Le funzioni di cui sopra sono per uso di base.

Se vuoi ESTENDERE la funzione ...

Si, puoi.

Sto usando molte API e una delle prime funzioni che integro in ogni pagina HTML è la prima funzione Ajax in questa risposta, con solo GET ...

Ma puoi fare molte cose con XMLHttpRequest 2:

Ho realizzato un download manager (utilizzando intervalli su entrambi i lati con curriculum, filereader, filesystem), vari convertitori di resizer di immagini usando canvas, popolando database SQL web con base64images e molto altro ... Ma in questi casi dovresti creare una funzione solo per quello scopo ... a volte hai bisogno di un blob, buffer di array, puoi impostare intestazioni, sovrascrivere il tipo Mime e c'è molto di più ...

Ma la domanda qui è come restituire una risposta Ajax ... (ho aggiunto un modo semplice).

326 BenjaminGruenbaum May 12 2015 at 09:22

Se stai usando le promesse, questa risposta è per te.

Ciò significa AngularJS, jQuery (con differito), sostituzione di XHR nativo (fetch), EmberJS, salvataggio di BackboneJS o qualsiasi libreria di nodi che restituisce promesse.

Il tuo codice dovrebbe essere qualcosa sulla falsariga di questo:

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 callback per AJAX. Ho una risposta per XHR nativo. Questa risposta è per l'uso generico delle promesse sul frontend o sul backend.


La questione centrale

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

Ogni volta che si chiama un metodo che restituisce una promessa, i thengestori vengono sempre eseguiti in modo asincrono, ovvero dopo il codice sottostante che non si trova in un .thengestore.

Ciò significa che quando stai restituendo datail thengestore che hai definito non è stato ancora eseguito. Questo 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 di dataè undefinedpoiché la data = 5parte non è stata ancora eseguita. Probabilmente verrà eseguito in un secondo, ma a quel punto è irrilevante per il valore restituito.

Poiché l'operazione non è ancora avvenuta (AJAX, chiamata server, IO, timer) stai restituendo il valore prima che la richiesta abbia la possibilità di dire al tuo codice qual è quel valore.

Una possibile soluzione a questo problema è codificare in modo reattivo , dicendo al programma cosa fare una volta completato il calcolo. Le promesse consentono attivamente questo essendo di natura temporale (sensibile al tempo).

Riepilogo rapido delle promesse

Una promessa è un valore nel tempo . Le promesse hanno uno stato, iniziano come in sospeso senza valore e possono essere fissate a:

  • soddisfatto, il che significa che il calcolo è stato completato con successo.
  • rifiutato significa che il calcolo non è riuscito.

Una promessa può cambiare stato solo una volta, dopodiché rimarrà sempre nello stesso stato per sempre. È possibile associare thengestori alle promesse per estrarne il valore e gestire gli errori. theni gestori consentono il concatenamento delle chiamate. Le promesse vengono create utilizzando le API che le restituiscono . Ad esempio, la più moderna sostituzione AJAX fetcho le $.getpromesse di ritorno di jQuery .

Quando chiediamo .thenuna promessa e restituiamo qualcosa da essa, otteniamo una promessa per il valore elaborato . Se restituiamo un'altra promessa, otterremo cose incredibili, ma teniamo i nostri cavalli.

Con promesse

Vediamo come possiamo risolvere il problema di cui sopra con le promesse. Innanzitutto, 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 thenper 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;
});

In sostanza, invece di restituire un valore di cui non possiamo fare a causa del modello di concorrenza - stiamo restituendo un involucro per un valore che siamo in grado di 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 imparato che non possiamo restituire valori da chiamate già asincrone, ma possiamo usare promesse e concatenarle per eseguire l'elaborazione. Ora sappiamo come restituire la risposta da una chiamata asincrona.

ES2015 (ES6)

ES6 introduce i generatori che sono funzioni che possono tornare nel mezzo e quindi riprendere il punto in cui si trovavano. 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 iterato. Anche se questo è interessante di per sé e apre spazio a molte possibilità, c'è un caso particolarmente interessante.

Se la sequenza che stiamo producendo è una sequenza di azioni piuttosto che di numeri, possiamo mettere in pausa la funzione ogni volta che viene restituita un'azione e aspettarla prima di riprendere 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 lo fanno per te, scriverne uno è di poche righe di codice ma va oltre lo scopo di questa risposta. Userò Bluebird's 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 stessa, 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 tutte puoi awaitpromettere. Questo è solo "zucchero" (sintassi migliore) per la proposta ES6 sopra, aggiungendo le parole chiave asynce await. Facendo 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
}

Restituisce comunque una promessa lo stesso :)

256 Nic May 23 2014 at 09:05

Stai usando Ajax in modo errato. L'idea è di non fare in modo che restituisca qualcosa, ma invece di trasferire i dati a qualcosa chiamato 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 trasferire i dati o fare quello che vuoi direttamente all'interno della funzione di successo.

242 HemantBavle Feb 19 2014 at 01:58

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);    
}); 
229 JohannesFahrenkrug Aug 11 2016 at 21:17

Risponderò con un fumetto disegnato a mano dall'aspetto orribile. La seconda immagine è il motivo per cui si resulttrova undefinednel tuo esempio di codice.

165 MaleenAbewardana Aug 26 2014 at 15:11

Angular1

Per le persone che utilizzano AngularJS , è possibile gestire questa situazione utilizzando Promises.

Qui dice

Le promesse possono essere utilizzate per annullare le funzioni asincrone e consentono di concatenare più funzioni insieme.

Puoi trovare una bella spiegazione anche qui .

Esempio trovato nei documenti menzionati di seguito.

  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 versioni successive

In Angular2con un'occhiata al seguente esempio, ma il suo raccomandato per l'uso Observablescon Angular2.

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

}

Puoi consumarlo in questo modo,

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

Guarda il post originale qui. Ma Typescript non supporta le promesse es6 native , se vuoi usarlo, potresti aver bisogno di plugin per questo.

Inoltre, ecco le specifiche delle promesse definite qui.

159 T.J.Crowder May 03 2017 at 23:59

La maggior parte delle risposte qui fornisce suggerimenti utili per quando si dispone di una singola operazione asincrona, ma a volte questo viene visualizzato quando è necessario eseguire un'operazione asincrona per ogni voce in un array o in un'altra struttura simile a un elenco. La tentazione è di fare questo:

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

Esempio:

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

Il motivo per cui non funziona è che i callback di doSomethingAsyncnon sono ancora stati eseguiti nel momento in cui stai tentando di utilizzare i risultati.

Quindi, se si dispone di un array (o di un elenco di qualche tipo) e si desidera eseguire operazioni asincrone per ciascuna voce, sono disponibili due opzioni: eseguire le operazioni in parallelo (sovrapposte) o in serie (una dopo l'altra in sequenza).

Parallelo

Puoi avviarli tutti e tenere traccia di quanti callback ti aspetti, quindi utilizzare i risultati quando hai ottenuto tanti callback:

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

Esempio:

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

(Potremmo farla finita expectinge usarla results.length === theArray.length, ma questo ci lascia aperti alla possibilità che theArrayvenga modificata mentre le chiamate sono in sospeso ...)

Si noti come utilizziamo indexfrom forEachper salvare il risultato nella resultsstessa posizione della voce a cui si riferisce, anche se i risultati arrivano fuori ordine (poiché le chiamate asincrone non vengono necessariamente completate nell'ordine in cui sono state avviate).

Ma cosa succede se devi restituire quei risultati da una funzione? Come hanno sottolineato le altre risposte, non puoi; devi fare in modo che la tua funzione accetti e richiami una richiamata (o restituisca una promessa ). Ecco una versione di callback:

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

Esempio:

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

Oppure ecco una versione che restituisce Promiseinvece un :

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

Ovviamente, se doSomethingAsyncci trasmettessero errori, rejectrifiuteremmo la promessa quando otteniamo un errore.)

Esempio:

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

(O in alternativa, potresti creare un involucro per doSomethingAsyncrestituire una promessa, e poi fare quanto segue ...)

Se doSomethingAsyncti dà una promessa , puoi usare 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 sai che doSomethingAsyncignorerà un secondo e un terzo argomento, puoi semplicemente passarlo direttamente a map( mapchiama il suo callback con tre argomenti, ma la maggior parte delle persone usa solo il primo per la maggior parte del tempo):

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

Esempio:

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

Nota che Promise.allrisolve la sua promessa con una serie dei risultati di tutte le promesse che gli dai quando sono tutte risolte, o rifiuta la sua promessa quando la prima delle promesse che le dai viene rifiutata.

Serie

Supponi di non volere che le operazioni siano in parallelo? Se si desidera eseguirli uno dopo l'altro, è necessario attendere il completamento di ciascuna operazione prima di avviare la successiva. Ecco un esempio di una funzione che lo fa e chiama un callback con il risultato:

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

(Dal momento che stiamo facendo il lavoro in serie, possiamo semplicemente usarlo results.push(result)poiché sappiamo che non otterremo risultati fuori ordine. In precedenza avremmo potuto usare results[index] = result;, ma in alcuni dei seguenti esempi non abbiamo un indice usare.)

Esempio:

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

(O, ancora, costruisci un involucro per doSomethingAsyncquesto ti dà una promessa e fai quanto segue ...)

Se doSomethingAsyncti dà una promessa, se puoi usare la sintassi di ES2017 + (magari con un transpiler come Babel ), puoi usare una asyncfunzione con for-ofe 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);
});

Esempio:

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 non puoi (ancora) usare la sintassi di ES2017 +, puoi usare una variazione sul pattern "Promise reduce" (questo è più complesso del solito Promise reduce perché non stiamo passando il risultato da uno a quello successivo, ma invece raccogliendo i risultati in un array):

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

Esempio:

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

... che è meno ingombrante con ES2015 + funzioni freccia :

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

Esempio:

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

113 FranciscoCarmona Jun 02 2016 at 15:31

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 (viene risolta al ritorno res.data.value). Quindi aspetti fino al completamento della richiesta $ http.get e quindi viene eseguito console.log (res.joke) (come un normale flusso asincrono).

Questo è il plnkr:

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

Modo ES6 (asincrono - attendi)

(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);
  });
})();
112 Alireza May 24 2017 at 16:38

Questo è uno dei due modi in cui l'associazione dati o il concetto di archivio utilizzati in molti nuovi framework JavaScript funzioneranno alla grande per te ...

Quindi, se stai usando Angular, React o qualsiasi altro framework che esegue l'associazione dei dati in due modi o il concetto di archivio, questo problema è semplicemente risolto per te, quindi in parole semplici, il tuo risultato è undefinednella prima fase, quindi hai result = undefinedprima di ricevere il dati, quindi non appena ottieni il risultato, verrà aggiornato e verrà assegnato al nuovo valore quale risposta della tua chiamata Ajax ...

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

Puoi utilizzare una richiamata , una promessa e osservabile di recente per gestirla per te, ad esempio nelle promesse abbiamo una funzione simile success()o then()che verrà eseguita quando i tuoi dati saranno pronti per te, lo stesso con la funzione di richiamata o di sottoscrizione su osservabile .

Ad esempio, nel tuo caso in cui 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, studiare le promesse e gli osservabili che sono modi più nuovi per eseguire queste operazioni asincrone.

105 AnishK. Nov 01 2017 at 03:12

È un problema molto comune che affrontiamo mentre siamo alle prese con i "misteri" di JavaScript. Fammi provare a demistificare questo mistero oggi.

Cominciamo 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 (in cui ogni riga di codice è "terminata con il suo lavoro" prima della successiva in sequenza) e il risultato è lo stesso previsto.

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

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

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

Quindi ecco fatto, quel ritardo ha appena rotto la funzionalità che ci aspettavamo! Ma cosa è successo esattamente? Bene, in realtà è abbastanza logico se guardi 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 1 s per restituire 'wohoo'. Ma come puoi vedere, il valore assegnato a bar è il materiale immediatamente restituito da foo (), che non è niente, cioè solo undefined.

Allora, come affrontiamo questo problema?

Chiediamo una PROMESSA alla nostra funzione . La promessa riguarda davvero ciò che significa: significa che la funzione ti garantisce di fornire qualsiasi output che riceverà 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'
});

Pertanto, il riepilogo è: per affrontare le funzioni asincrone come le chiamate basate su Ajax, ecc., È possibile utilizzare una promessa al resolvevalore (che si intende restituire). Quindi, in breve, si risolve il valore invece di restituire , nelle funzioni asincrone.

AGGIORNAMENTO (promette con async / await)

Oltre a utilizzare 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 di codice successiva. È ancora solo promisessotto il cofano, ma con un approccio sintattico diverso. Per rendere le cose più chiare, puoi trovare un confronto di seguito:

quindi / cattura versione:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
          console.error(err);
       })
 }

versione asincrona / attesa:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
101 jsbisht Sep 02 2015 at 19:54

Un altro approccio per restituire un valore da una funzione asincrona consiste nel passare un oggetto che memorizzerà il risultato della 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.

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

89 rohithpr Jan 26 2016 at 00:43

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

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

Finiresti per attraversare async1; controlla se nameè indefinito o meno e chiama la richiamata di conseguenza.

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

async1(name, async2)

Anche se 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 qui .

88 loretoparisi Apr 13 2016 at 05:55

Il seguente esempio che ho scritto mostra come farlo

  • Gestire le chiamate HTTP asincrone;
  • Attendi la risposta da ogni chiamata API;
  • Usa lo schema Promise ;
  • Usa il pattern Promise.all per unirti a più chiamate HTTP;

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

Contesto. L'esempio sta eseguendo una query sull'endpoint Spotify Web API per cercare playlistoggetti per un determinato insieme di stringhe di query:

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

Per ogni elemento, una nuova promessa attiverà un blocco ExecutionBlock, analizzerà il risultato, pianificherà una nuova serie di promesse in base all'array dei risultati, ovvero un elenco di useroggetti Spotify ed eseguirà la nuova chiamata HTTP 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 searchAPI Spotify recenti richiederanno la specifica di un token di accesso nelle intestazioni della richiesta:

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

Quindi, per eseguire il seguente esempio, devi inserire il tuo 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 questa soluzione qui .

84 PabloMatiasGomez Apr 22 2016 at 21:47

La risposta breve è che devi implementare una richiamata come questa:

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

$.ajax({
    url: "...",
    success: callback
});
82 mikemaccana Jun 02 2017 at 16:51

Risposta 2017: ora puoi fare esattamente quello che vuoi in ogni browser e nodo corrente

Questo è abbastanza semplice:

  • Restituisci una promessa
  • Usa "await" , che dirà a JavaScript di attendere che la promessa venga risolta in un valore (come la risposta HTTP)
  • Aggiungi la parola chiave "async" alla funzione principale

Ecco una versione funzionante del tuo codice:

(async function(){

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

})()

await è supportato in tutti i browser attuali e nel nodo 8

80 AniketJha Feb 03 2018 at 13:06

Js è un singolo thread.

Il browser può essere diviso in tre parti:

1) Event Loop

2) API Web

3) Coda eventi

Event Loop viene eseguito per sempre, ovvero una sorta di loop infinito. L'Event Queue è dove tutte le tue funzioni vengono spinte su qualche evento (esempio: clic) questo viene eseguito uno ad uno dalla coda e messo in Event loop che esegue questa funzione e si prepara da solo per quella successiva dopo che la prima è stata eseguita. Ciò significa che l'esecuzione di una funzione non viene avviata fino a quando la funzione prima di essere in coda non viene eseguita nel ciclo di eventi.

Ora pensiamo di aver inserito due funzioni in una coda, una è per ottenere un dato dal server e un'altra utilizza quei dati. Abbiamo inserito prima la funzione serverRequest () in coda e poi la funzione utiliseData (). La funzione serverRequest va nel loop degli eventi ed effettua una chiamata al server poiché non sappiamo mai quanto tempo ci vorrà per ottenere i dati dal server, quindi questo processo dovrebbe richiedere tempo e quindi abbiamo occupato il nostro loop degli eventi appendendo la nostra pagina, è lì L'API entra nel ruolo prende questa funzione dal loop degli eventi e si occupa del server che rende libero il loop degli eventi in modo che possiamo eseguire la funzione successiva dalla coda.La funzione successiva in coda è utiliseData () che va in loop ma a causa di nessun dato disponibile va lo spreco e l'esecuzione della funzione successiva continua fino alla fine della coda (si chiama chiamata asincrona, cioè possiamo fare qualcos'altro finché non otteniamo dati)

Supponiamo che la nostra funzione serverRequest () avesse un'istruzione return in un codice, quando recuperiamo i dati dall'API Web del server, li metterà in coda alla fine della coda. Quando viene inserito alla fine in coda, non possiamo utilizzare i suoi dati poiché non è rimasta alcuna funzione nella nostra coda per utilizzare questi dati. Pertanto non è possibile restituire qualcosa da Async Call.

Quindi la soluzione a questo è richiamata o promessa .

Un'immagine da una delle risposte qui, spiega correttamente l'uso della richiamata ... Diamo la nostra funzione (funzione che utilizza i dati restituiti dal server) alla funzione che chiama il server.

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

Richiamata Javscript.info

70 VinothRajendran May 26 2016 at 20:26

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

Esempio di utilizzo semplice:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
70 amaksr May 27 2017 at 09:47

Un'altra soluzione è eseguire il codice tramite l'esecutore sequenziale nsynjs .

Se la funzione sottostante è promessa

nsynjs valuterà tutte le promesse in sequenza e inserirà il risultato della promessa nella 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. Avvolgi la funzione con callback nel wrapper compatibile con nsynjs (se ha una versione promessa, puoi saltare questo passaggio):

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. Metti in funzione la logica sincrona:

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

Passaggio 3. Esegui la funzione in modo sincrono tramite nsynjs:

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

Nsynjs valuterà tutti gli operatori e le espressioni passo dopo passo, sospendendo 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

42 James Feb 17 2018 at 22:26

ECMAScript 6 dispone di "generatori" che consentono di programmare facilmente in uno stile asincrono.

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

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 è necessario scegliere come target browser che non supportano ES6, è possibile eseguire il codice tramite Babel o il compilatore di chiusura per generare ECMAScript 5.

I callback ...argssono racchiusi in una matrice e destrutturati quando vengono letti in modo che il pattern possa far fronte ai callback che hanno più argomenti. Ad esempio con node fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
39 MohanDere Aug 13 2016 at 16:36

Di seguito sono riportati alcuni approcci per lavorare con le richieste asincrone:

  1. Oggetto Promessa del browser
  2. D - Una libreria di promesse per JavaScript
  3. A + Promises.js
  4. jQuery differita
  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();

38 4revs,3users92%user663031 Jan 23 2016 at 10:28

Ci troviamo in un universo che sembra progredire lungo una dimensione che chiamiamo "tempo". Non capiamo veramente che cosa sia il tempo, ma abbiamo sviluppato astrazioni e vocaboli che ci permettono di ragionare e di 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 quelle prime cose alla fine si verificano. Questa è la nozione di base chiamata "asincronicità". Nel nostro mondo sempre più connesso in rete, il caso più comune di asincronicità è l'attesa che qualche sistema remoto risponda a qualche richiesta.

Considera un esempio. Chiami il lattaio e ordini del latte. Quando arriva, lo vuoi mettere nel tuo caffè. Non puoi mettere il latte nel tuo caffè in questo momento, perché non è ancora qui. Devi aspettare che arrivi prima di metterlo nel tuo caffè. In altre parole, quanto segue non funzionerà:

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

Perché JS non ha modo di sapere che ha bisogno di aspettare per order_milkalla fine prima di eseguire put_in_coffee. In altre parole, non sa che order_milkè asincrono, è qualcosa che non produrrà latte fino a qualche tempo futuro. JS e altri linguaggi dichiarativi eseguono un'istruzione dopo l'altra senza attendere.

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

order_milk(put_in_coffee);

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

Il problema con questo approccio di callback è che inquina la normale semantica di una funzione che riporta il suo risultato con return; invece, le funzioni non devono riportare i loro risultati chiamando un callback dato come parametro. Inoltre, questo approccio può diventare rapidamente ingombrante quando si tratta di sequenze di eventi più lunghe. Ad esempio, diciamo che voglio aspettare che il latte venga messo nel caffè, e poi e solo allora eseguo un terzo passaggio, ovvero bere il caffè. Finisco per aver bisogno di scrivere qualcosa del genere:

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

dove passo put_in_coffeesia al milk per inserirlo, sia all'action ( drink_coffee) da eseguire una volta che il milk è stato inserito. Tale codice diventa difficile da scrivere, leggere ed eseguire 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 le promesse

Questa era la motivazione per la nozione di "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 accadrà 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 eventi futuri ("concatenamento"):

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

Applichiamo le promesse al tuo problema particolare. Avvolgeremo 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 returna alla chiamata a $.ajax. Funziona perché jQuery $.ajaxrestituisce già una sorta di cosa simile a una promessa. (In pratica, senza entrare nei dettagli, preferiremmo avvolgere questa chiamata in modo da restituire una vera promessa, o utilizzare qualche alternativa a $.ajaxquella lo fa.) Ora, se vogliamo caricare il file e aspettare che finisca e poi fai qualcosa, possiamo semplicemente dire

get_data() . then(do_something)

per esempio,

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

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

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 la seconda". 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 prossima versione di ES ma è già disponibile nei transpiler come Babel con i giusti preset. Questo ci permette di scrivere semplicemente

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

Nel tuo caso, potresti scrivere qualcosa di simile

async function foo() {
  data = await get_data();
  console.log(data);
}
37 DavidRTribble Sep 24 2015 at 05:52

Risposta breve : il foo()metodo viene restituito 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 restituita.

Diverse soluzioni sono state fornite in questo thread. Forse il modo più semplice è passare un oggetto al foo()metodo e memorizzare i risultati in un membro di quell'oggetto dopo il completamento 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

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

36 MahfuzurRahman Apr 24 2017 at 15:09

Usa una callback()funzione all'interno del foo()successo. Prova in questo modo. È semplice e facile da capire.  

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();
30 AmirFo Dec 07 2018 at 21:10

Utilizzando Promise

La risposta più perfetta a questa domanda sta usando 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);
  });
}

Utilizzo

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

Ma aspetta...!

C'è un problema con l'utilizzo delle promesse!

Perché dovremmo usare la nostra Promessa personalizzata?

Stavo usando questa soluzione per un po 'finché non ho capito che c'era un errore nei vecchi browser:

Uncaught ReferenceError: Promise is not defined

Quindi ho deciso di implementare la mia classe Promise per ES3 sotto i compilatori js se non è definito. Basta aggiungere questo codice prima del codice principale e quindi utilizzare in sicurezza 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;
    }();
}
29 PieterJanBonestroo Jan 14 2018 at 02:13

La domanda era:

Come restituisco la risposta da una chiamata asincrona?

che può essere interpretato come:

Come rendere sincrono il codice asincrono ?

La soluzione sarà evitare i callback e utilizzare una combinazione di Promises e async / await .

Vorrei fare un esempio per una richiesta Ajax.

(Sebbene possa essere scritto in Javascript, preferisco scriverlo in Python e compilarlo in Javascript usando Transcrypt . Sarà abbastanza chiaro.)

Abilita prima l'utilizzo di JQuery, per avere a $disposizione come S:

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

Definisci una funzione che restituisca una promessa , in questo caso una chiamata 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()

Usa il codice asincrono come se fosse sincrono :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
28 KhoaBui Jul 06 2017 at 03:28

Ovviamente ci sono molti approcci come la richiesta sincrona, la promessa, ma dalla mia esperienza penso che dovresti usare l'approccio di richiamata. È naturale il comportamento asincrono di Javascript. Quindi, il tuo frammento 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.
}
26 SanjiMika Jan 20 2020 at 05:23

Dopo aver letto tutte le risposte qui e con le mie esperienze, vorrei riprendere i dettagli di callback, promise and async/awaitper la programmazione asincrona in JavaScript.

1) Callback: il motivo fondamentale per un callback è l'esecuzione di codice in risposta a un evento (vedere l'esempio seguente). Usiamo la richiamata in JavaScript ogni volta.

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

Ma se è necessario utilizzare molti callback annidati nell'esempio seguente, sarà terribile per il refactoring del codice.

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

2) Promise: una sintassi ES6 - Promise risolve il problema dell'inferno di callback!

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code. 
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
}) 

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

myFirstPromise è un'istanza di Promise che rappresenta il processo di codici asincroni. La funzione di risoluzione segnala che l'istanza di Promise è terminata. Successivamente, possiamo chiamare .then () (una catena di .then come vuoi) e .catch () sull'istanza della promessa:

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

3) Async / Await: una nuova sintassi ES6 - Await è fondamentalmente la sintassi zuccherina di Promise!

La funzione asincrona ci fornisce una sintassi pulita e concisa che ci consente di scrivere meno codice per ottenere lo stesso risultato che otterremmo con le promesse. Async / Await è simile al codice sincrono e il codice sincrono è molto più facile da leggere e scrivere. Per rilevare errori con Async / Await, possiamo usare il blocco try...catch. Qui non è necessario scrivere una catena di .then () di sintassi Promise.

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

Conclusione: queste sono totalmente le tre sintassi per la programmazione asincrona in JavaScript che dovresti capire bene. Quindi, se possibile, ti consiglio di usare "promise" o "async / await" per il refactoring dei tuoi codici asincroni (principalmente per richieste XHR) !

20 MatthewBrent May 04 2018 at 22:56

Piuttosto che lanciarti del codice, ci sono 2 concetti che sono fondamentali per capire come JS gestisce i callback e l'asincronicità. (è anche solo una parola?)

Il ciclo di eventi e il modello di concorrenza

Ci sono tre cose di cui devi essere consapevole; La fila; il ciclo di eventi e lo stack

In termini ampi e semplicistici, il ciclo di eventi è come il project manager, è in costante ascolto di tutte le funzioni che vogliono eseguire e comunica tra la coda e lo stack.

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

Una volta ricevuto un messaggio per eseguire qualcosa, lo aggiunge alla coda. La coda è l'elenco delle cose che sono in attesa di essere eseguite (come la tua richiesta AJAX). immaginalo in questo modo:

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

Quando uno di questi messaggi sta per essere eseguito, estrae il messaggio dalla coda e crea uno stack, lo stack è tutto ciò che JS deve eseguire per eseguire l'istruzione nel messaggio. Quindi nel nostro esempio gli viene detto di chiamarefoobarFunc

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

Quindi tutto ciò che foobarFunc deve eseguire (nel nostro caso anotherFunction) verrà inserito nello stack. eseguito, e poi dimenticato - il ciclo di eventi si sposterà quindi sulla prossima cosa nella coda (o ascolterà i messaggi)

La cosa fondamentale qui è l'ordine di esecuzione. Questo è

QUANDO sta per funzionare qualcosa

Quando effettui una chiamata utilizzando AJAX a una parte esterna o esegui un codice asincrono (un setTimeout per esempio), Javascript dipende da una risposta prima di poter procedere.

La grande domanda è quando riceverà la risposta? La risposta è che non lo sappiamo, quindi il ciclo di eventi attende che il messaggio dica "hey run me". Se JS aspettasse solo quel messaggio in modo sincrono, la tua app si bloccherebbe e farà schifo. Quindi JS continua a eseguire l'elemento successivo nella coda in attesa che il messaggio venga aggiunto di nuovo alla coda.

Ecco perché con la funzionalità asincrona usiamo cose chiamate callback . È un po 'come una promessa letteralmente. Come in Prometto di restituire qualcosa ad un certo punto jQuery utilizza callback specifici chiamati deffered.done deffered.faile deffered.always(tra gli altri). Puoi vederli tutti qui

Quindi quello che devi fare è passare una funzione che è promessa di essere eseguita a un certo punto con i dati che le vengono passati.

Perché un callback non viene eseguito immediatamente ma in un secondo momento è importante passare il riferimento alla funzione non eseguita. così

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

quindi la maggior parte del tempo (ma non sempre) si passa foononfoo()

Si spera che abbia un senso. Quando incontri cose come questa che sembrano confuse, consiglio vivamente di leggere completamente la documentazione per comprenderla almeno. Ti renderà uno sviluppatore molto migliore.