Come restituisco la risposta da una chiamata asincrona?


5512

Ho una funzione fooche fa una richiesta Ajax. Come posso restituire la risposta foo?

Ho provato a restituire il valore dal successcallback, nonché 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`.

Risposte:


5704

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

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

Il problema

La A in Ajax sta per asincrono . Ciò significa che l'invio della richiesta (o piuttosto la ricezione della risposta) viene escluso dal normale flusso di esecuzione. Nel tuo esempio, $.ajaxrestituisce 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, rende 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 ', aspetti al telefono e guardi nello spazio, finché il tuo amico non ti dà la risposta di cui hai 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 findItempotrebbe richiedere molto tempo per l'esecuzione, qualsiasi codice successivo var item = findItem();deve attendere 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 dovrebbe richiamarti sul tuo cellulare. Riattacchi, 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 si effettua una richiesta Ajax.

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

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


Soluzione (s)

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

Perché è male chiedi?

JavaScript viene eseguito nel thread dell'interfaccia utente del browser e qualsiasi processo di lunga durata bloccherà l'interfaccia utente, rendendola non rispondente. Inoltre, esiste un limite massimo per i tempi di esecuzione di JavaScript e il browser chiederà all'utente se continuare o meno l'esecuzione.

Tutto ciò è un'esperienza utente davvero negativa. L'utente non sarà in grado di dire se tutto funziona bene o no. 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:

  • Promesse conasync/await (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 promessa)

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


ES2017 +: promesse 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 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"scartare" una promessa e determinare il valore con cui la promessa è stata risolta o genera un errore se la promessa è stata respinta.

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

Puoi leggere di più su asynce awaitsu 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;
  }
}

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

Supporto delle versioni correnti di browser e nodiasync/await . Puoi anche supportare ambienti più vecchi trasformando il tuo codice in ES5 con l'aiuto di rigeneratore (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. L'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, è possibile fooaccettare un callback e utilizzarlo come successcallback. 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 di 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 semplicemente a success. Cioè una volta che la richiesta Ajax ha esito positivo,$.ajax chiamerà callbacke passerà la risposta al callback (a cui si può fare riferimento con result, poiché è così che abbiamo definito il callback).

È inoltre possibile elaborare la risposta prima di passarla al callback:

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. Dopotutto, JavaScript nel browser è fortemente guidato dagli eventi (eventi DOM). Ricevere la risposta Ajax non è altro che un evento.
Potrebbero sorgere difficoltà quando si deve lavorare con codice di terze parti, ma la maggior parte dei problemi può essere risolta semplicemente riflettendo sul flusso dell'applicazione.


ES2015 +: promesse con then ()

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

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

Il vantaggio rispetto ai semplici callback è 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).
  });

Applicati 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 offerti dall'offerta promessa va oltre lo scopo di questa risposta, ma se scrivi un nuovo codice, dovresti prenderli seriamente in considerazione. Forniscono un'ottima astrazione e separazione del codice.

Maggiori informazioni sulle promesse: HTML5 rock - Promesse JavaScript

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: gotchas promettenti

Tieni presente che le promesse e gli oggetti differiti sono solo contenitori per un valore futuro, non sono il valore stesso. Ad esempio, supponiamo 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 sopra indicati. 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 jQuery Ajax Deferred, non la risposta dal server. Ciò significa che l' ifistruzione otterrà sempre questo oggetto differito, lo considererà come truee procederà come se l'utente avesse effettuato l'accesso. Non va bene.

Ma la correzione è 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
});

Sconsigliato: chiamate "Ajax" sincrone

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

Senza jQuery

Se si utilizza direttamente un XMLHTTPRequestoggetto, passare falsecome terzo argomento a .open.

jQuery

Se si utilizza jQuery , è possibile impostare l' asyncopzione su false. Nota che questa opzione è obsoleta da jQuery 1.8. È quindi possibile utilizzare ancora un successcallback o accedere alla responseTextproprietà dell'oggetto jqXHR :

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

Se si utilizza qualsiasi altro metodo jQuery Ajax, come $.get, $.getJSONecc., È necessario modificarlo in $.ajax(poiché è possibile solo passare i parametri di configurazione a $.ajax).

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


74
@Pommy: se si desidera utilizzare jQuery, è necessario includerlo. Fare riferimento a docs.jquery.com/Tutorials:Getting_Started_with_jQuery .
Felix Kling,

11
Nella soluzione 1, sub jQuery, non riuscivo a capire questa riga: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Sì, mi rendo conto che il mio nick è un po 'ironico in questo caso)
cssyphus,

32
@gibberish: Mmmh, non so come possa essere chiarito. Vedi come fooviene chiamato e una funzione gli viene passata ( foo(function(result) {....});)? resultviene utilizzato all'interno di questa funzione ed è la risposta della richiesta Ajax. Per fare riferimento a questa funzione, viene chiamato callbacke assegnato il primo parametro di foo successanziché una funzione anonima. Quindi, $.ajaxchiamerà callbackquando la richiesta ha avuto esito positivo. Ho provato a spiegarlo un po 'di più.
Felix Kling,

43
La chat per questa domanda è morta, quindi non sono sicuro di dove proporre le modifiche delineate, ma io propongo: 1) Cambia la parte sincrona in una semplice discussione del perché non va bene con nessun esempio di codice su come farlo. 2) Rimuovere / unire gli esempi di callback per mostrare solo l'approccio differito più flessibile, che penso possa essere anche un po 'più facile da seguire per coloro che imparano Javascript.
Chris Moschini,

14
@Jessi: penso che tu abbia frainteso quella parte della risposta. Non è possibile utilizzare $.getJSONse si desidera che la richiesta Ajax sia sincrona. Tuttavia, non si dovrebbe desiderare che la richiesta sia sincrona, quindi non si applica. Dovresti utilizzare callback o promesse per gestire la risposta, come spiegato in precedenza nella risposta.
Felix Kling,

1071

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

Il tuo codice dovrebbe essere qualcosa del genere:

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 quelli che usano il nuovofetch API, Angular o promesse ho aggiunto un'altra risposta di seguito )


Quello che stai affrontando

Questo è un breve riassunto di "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 piuttosto la ricezione della risposta) viene escluso dal normale flusso di esecuzione. Nel tuo esempio, .sendrestituisce immediatamente e l'istruzione successiva return result;, viene eseguita prima della funzione che hai passatosuccess callback fosse chiamata.

Ciò significa che quando ritorni, l'ascoltatore 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 areso è 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 nuovamente attivamente , dicendo al programma cosa fare al termine del 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 passandogetFive un'azione da eseguire al completamento, stiamo dicendo al nostro codice come reagire al completamento di un evento (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 sostanzialmente due modi per risolverlo:

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

1. Synchronous AJAX - Non farlo !!

Per quanto riguarda AJAX sincrono, non farlo! La risposta di Felix solleva alcuni argomenti convincenti sul perché è 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 ... ... ciò 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. Ristruttura il codice

Lascia che la tua funzione accetti una richiamata. Nel codice di esempio foopuò essere fatto accettare un callback. Diremo al nostro codice come reagire al footermine.

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 anche passare facilmente un riferimento a una funzione esistente, facendola apparire come:

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

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

Ora, definiamo foo stesso di 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 si completa correttamente, possiamo estenderlo ulteriormente controllando se lo stato della risposta non è 200 e agendo di conseguenza (creare un gestore di errori e simili). Risolvere efficacemente il nostro problema.

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


20
"richieste sincrone bloccano l'esecuzione di codice e possono perdere memoria ed eventi" come può una richiesta sincrona perdere memoria?
Matthew G,

10
@MatthewG Ho aggiunto una taglia su questa domanda , vedrò cosa posso pescare. Nel frattempo sto rimuovendo la citazione dalla risposta.
Benjamin Gruenbaum,

17
Solo per riferimento, XHR 2 ci consente di utilizzare il onloadgestore, che si attiva solo quando lo readyStateè 4. Naturalmente, non è supportato in IE8. (Iirc, potrebbe aver bisogno di conferma.)
Florian Margaine,

9
La tua spiegazione su come passare una funzione anonima come callback è valida ma fuorviante. L'esempio var bar = foo (); sta chiedendo di definire una variabile, mentre il tuo suggerito foo (functim () {}); non definisce bar
Robbie Averill

396

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 suggerisco 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. Il callback viene impostato direttamente (quindi nessuna chiusura extra non necessaria).
  3. Utilizza il nuovo onload (quindi non è necessario verificare lo stato di && pronto)
  4. Ci sono alcune 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 si bind()richiama una lezione:

e.target.response

Esempio:

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

Oppure (quello sopra è che 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 persino il nome della variabile XMLHttpRequest. È sbagliato.

Check-out funzionalità avanzate di XMLHttpRequest

Supportava tutti i * browser moderni. E posso confermare come 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 si desidera 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 di scambio onload / oreadystatchange altrimenti lo si è 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 ottiene e pubblica.

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

O passa un elemento full form ( 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 menzionato nel commento, l'uso dell'errore && sincrono spezza completamente il punto della risposta. Qual è un bel modo breve per usare Ajax nel modo giusto?

Gestore 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 sopra, hai un gestore di 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 errato, 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 genererà un errore.

Anche se passi 'fdggdgilfdghfldj' come formdata non genererà 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 di post giusti.

tra domini non consentiti genera automaticamente errori.

Nella risposta all'errore, non ci sono codici di errore.

C'è solo ciò this.typeche è impostato su errore.

Perché aggiungere un gestore errori se non si ha alcun controllo sugli errori? La maggior parte degli errori viene restituita al suo interno nella funzione di richiamata displayAjax().

Quindi: non è necessario verificare gli errori se si è in grado di copiare e incollare correttamente l'URL. ;)

PS: Come primo test ho scritto x ('x', displayAjax) ... e ho avuto 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 TROVERAI . Ho LOL'd


Leggi un file sincrono

Non farlo.

Se vuoi bloccare il browser per un po ', carica un bel .txtfile sincrono di grandi dimensioni.

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 esiste 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 i file del tuo elenco o qualunque cosa usi sempre funzioni diverse per ogni richiesta ...

Solo se hai una pagina in cui carichi sempre lo stesso XML / JSON o qualsiasi altra cosa ti serva una sola funzione. In tal caso, modifica leggermente la funzione Ajax e sostituisci b con la tua funzione speciale.


Le funzioni sopra sono per uso base.

Se si desidera ESTENDERE la funzione ...

Si, puoi.

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

Ma puoi fare molte cose con XMLHttpRequest 2:

Ho creato un download manager (usando intervalli su entrambi i lati con curriculum, filereader, filesystem), vari convertitori di resizer di immagini usando canvas, popolando database web SQL 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 le intestazioni, sovrascrivere il mimetype e c'è molto altro ...

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


15
Mentre questa risposta è buona (E tutti amiamo XHR2 e pubblicare dati di file e dati multipart è assolutamente fantastico) - questo mostra lo zucchero sintattico per pubblicare XHR con JavaScript - potresti voler metterlo in un post sul blog (mi piacerebbe) o anche in una biblioteca (non sono sicuro del nome x, ajaxo xhrpotrebbe essere più bello :)). Non vedo come si risolve restituendo la risposta da una chiamata AJAX. (qualcuno potrebbe ancora fare var res = x("url")e non capire perché non funziona;)). Una nota a margine: sarebbe bello se tornassi cdal metodo in modo che gli utenti possano agganciarsi errorecc.
Benjamin Gruenbaum

25
2.ajax is meant to be async.. so NO var res=x('url')..Questo è il punto centrale di questa domanda e risposte :)
Benjamin Gruenbaum,

3
perché c'è un parametro 'c' nelle funzioni, se nella prima riga stai sovrascrivendo qualunque valore avesse? mi sto perdendo qualcosa?
Brian H.

2
È possibile utilizzare i parametri come segnaposto per evitare di scrivere più volte "var"
cocco

11
@cocco Quindi hai scritto un codice fuorviante e illeggibile in una risposta SO per salvare alcune sequenze di tasti? Per favore, non farlo.
pietra

316

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

Ciò significa AngularJS, jQuery (con differito), 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 callback per AJAX. Ho una risposta per XHR nativo. Questa risposta è per l'uso generico delle promesse sul frontend o sul backend.


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 thengestori vengono sempre eseguiti in modo asincrono, ovvero dopo il codice sotto di loro che non è in un.then gestore.

Ciò significa che quando si restituisce datailthen gestore 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è undefineddaldata = 5 parte 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 al server, IO, timer) stai restituendo il valore prima che la richiesta abbia la possibilità di dire al tuo codice quale sia quel valore.

Una possibile soluzione a questo problema è codificare nuovamente attivamente , dicendo al programma cosa fare al termine del calcolo. Le promesse attivamente abilitano ciò essendo temporali (sensibili al tempo) in natura.

Riepilogo rapido delle promesse

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

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

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

Quando invochiamo .thenuna promessa e restituiamo qualcosa da essa, riceviamo una promessa per il valore elaborato . Se restituiamo un'altra promessa avremo cose straordinarie, ma teniamo i nostri cavalli.

Con le promesse

Vediamo come possiamo risolvere il problema di cui sopra con le promesse. Innanzitutto, dimostriamo la nostra comprensione degli stati promettenti dall'alto usando 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 usare 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 le promesse e incatenarle per eseguire l'elaborazione. Ora sappiamo come restituire la risposta da una chiamata asincrona.

ES2015 (ES6)

ES6 introduce generatori che sono funzioni che possono tornare nel mezzo e quindi riprendere il punto in cui si trovavano. Questo è generalmente 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 sequenza1,2,3,3,3,3,.... che può essere iterato. Mentre questo è interessante da solo e apre spazio a molte possibilità, c'è un caso particolare interessante.

Se la sequenza che stiamo producendo è una sequenza di azioni piuttosto che numeri, possiamo mettere in pausa la funzione ogni volta che viene prodotta un'azione e aspettarla prima di riprendere la funzione. Quindi, invece di una sequenza di numeri, abbiamo bisogno di una sequenza di futuro valori , ovvero: promesse.

Questo trucco un po 'complicato ma molto potente ci consente di scrivere codice asincrono in modo sincrono. Esistono diversi "corridori" che fanno questo per te, scrivendo uno è un paio di righe di codice ma va 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 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, al momento ci sono diverse proposte, ma in tutte puoi awaitprometterlo. 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 :)


Questa dovrebbe essere la risposta accettata. +1 per asincrono / attendi (anche se non dovremmo return await data.json();?)
Lewis Donovan,

247

Si sta utilizzando Ajax in modo errato. L'idea è di non restituire nulla, ma invece di consegnare 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 qualsiasi cosa nel gestore di invio non farà nulla. Devi invece consegnare i dati o fare ciò che vuoi direttamente all'interno della funzione di successo.


13
Questa risposta è completamente semantica ... il tuo metodo di successo è solo una richiamata all'interno di una richiamata. Potresti semplicemente avere success: handleDatae funzionerebbe.
Jacques ジ ャ ッ ク

5
E se si desidera restituire il "responseData" al di fuori di "handleData" ... :) ... come lo farai ...? ... perché un semplice ritorno lo restituirà al "successo" callback di ajax ... e non al di fuori di "handleData" ...
pesho hristov,

@Jacques & @pesho hristov Hai perso questo punto. Il gestore di invio non è il successmetodo, è l'ambito circostante di $.ajax.
travnik,

@travnik Non me lo sono perso. Se prendessi il contenuto di handleData e lo mettessi nel metodo di successo, si comporterebbe esattamente lo stesso ...
Jacques ジ ャ ッ ク

234

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

3
Non so chi l'abbia votata negativa. Ma questo è un lavoro che ha funzionato, infatti ho usato questo approccio per creare un'intera applicazione. Jquery.ajax non restituisce i dati, quindi è meglio usare l'approccio sopra. Se è sbagliato, ti preghiamo di spiegare e suggerire un modo migliore per farlo.
Hemant Bavle,

11
Scusa, ho dimenticato di lasciare un commento (di solito lo faccio!). L'ho sottovalutato. I downgrade non indicano correttezza fattuale o mancanza di, indicano utilità nel contesto o mancanza di. Non trovo la tua risposta utile dato Felix's che già lo spiega solo in modo molto più dettagliato. In una nota a margine, perché dovresti restringere la risposta se è JSON?
Benjamin Gruenbaum,

5
ok .. @Benjamin ho usato stringify, per convertire un oggetto JSON in stringa. E grazie per aver chiarito il tuo punto. Terrò a mente di pubblicare risposte più elaborate.
Hemant Bavle,

E se si desidera restituire "responseObj" al di fuori di "successCallback" ... :) ... come lo farai ...? ... perché un semplice ritorno lo restituirà al "successo" callback di ajax ... e non al di fuori di "successCallback" ...
pesho hristov,

221

Risponderò con un fumetto dall'aspetto orribile, disegnato a mano. La seconda immagine è il motivo per cui resultè undefinednell'esempio di codice.

inserisci qui la descrizione dell'immagine


32
Un'immagine vale più di mille parole , Persona A - Chiedi alla persona B i dettagli per riparare la sua auto, a sua volta Persona B - Esegue la chiamata Ajax e attende la risposta dal server per i dettagli di riparazione dell'auto, quando viene ricevuta la risposta, la funzione Ajax Success chiama la persona B e passa la risposta come argomento, la persona A riceve la risposta.
Shaijut,

10
Sarebbe fantastico se aggiungessi righe di codice con ogni immagine per illustrare i concetti.
Hassan Baig,

1
Nel frattempo, il ragazzo con la macchina è bloccato sul lato della strada. Si richiede la vettura viene fissato prima di continuare. Ora è solo sul lato della strada in attesa ... Preferirebbe essere al telefono in attesa di cambiamenti di stato ma il meccanico non lo farebbe ... Il meccanico ha detto che deve andare avanti con il suo lavoro e non può semplicemente uscire al telefono. Il meccanico ha promesso che lo avrebbe richiamato il prima possibile. Dopo circa 4 ore, il ragazzo si arrende e chiama Uber. - Esempio di timeout.
barrypicker,

@barrypicker :-D Brilliant!
Johannes Fahrenkrug,

159

Angular1

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

Qui dice

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

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

Vedi il post originale qui. Ma Typescript non supporta le promesse es6 native , se si desidera utilizzarlo, potrebbe essere necessario un plug-in.

Inoltre, ecco le promesse specificate qui.


15
Questo non spiega come le promesse risolverebbero affatto questo problema.
Benjamin Gruenbaum,

4
Anche i metodi jQuery e fetch restituiscono promesse. Suggerirei di rivedere la tua risposta. Anche se jQuery non è proprio lo stesso (allora c'è, ma catch non lo è).
Tracker1,

153

La maggior parte delle risposte qui fornisce suggerimenti utili per quando si ha una singola operazione asincrona, ma a volte, questo si presenta quando è necessario eseguire un'operazione asincrona per ogni voce in un array o 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:

La ragione per cui non funziona è che i callback da doSomethingAsyncnon sono ancora stati eseguiti quando si tenta di utilizzare i risultati.

Pertanto, 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: Esegui 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 così 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:

(Potremmo eliminare expectinge semplicemente usare results.length === theArray.length, ma questo ci lascia aperti alla possibilità che theArrayviene modificata mentre le chiamate sono in sospeso ...)

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

E se fosse necessario restituire quei risultati da una funzione? Come hanno indicato le altre risposte, non puoi; devi avere la tua funzione accettare e chiamare un callback (o restituire una Promessa ). Ecco una versione di richiamata:

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:

Oppure ecco una versione che restituisce un Promiseinvece:

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

Naturalmente, se doSomethingAsyncci passassero errori, useremmo rejectper rifiutare la promessa quando avessimo ricevuto un errore.)

Esempio:

(O in alternativa, potresti creare un wrapper per doSomethingAsyncrestituire una promessa, quindi 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 il più delle volte):

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

Esempio:

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

Serie

Supponiamo che non desideri che le operazioni siano in parallelo? Se si desidera eseguirli uno dopo l'altro, è necessario attendere il completamento di ciascuna operazione prima di iniziare 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);
});

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

Esempio:

(O, ancora una volta, crea un wrapper per doSomethingAsyncquesto ti dà una promessa e fai quanto segue ...)

Se doSomethingAsyncti dà una Promessa, se puoi usare la sintassi ES2017 + (forse 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:

Se non è possibile utilizzare la sintassi ES2017 + (ancora), è possibile utilizzare una variante del modello " Riduzione promessa" (questo è più complesso della normale riduzione Promessa perché non stiamo passando il risultato da uno al successivo, ma invece raccogliendo i loro 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:

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

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:


1
Potresti spiegare come funziona la if (--expecting === 0)parte del codice per favore? La versione di callback della tua soluzione funziona alla grande per me, non capisco come, con questa affermazione, stai controllando il numero di risposte completate. Apprezzo la mancanza di conoscenza da parte mia. Esiste un modo alternativo per scrivere il controllo?
Sarah,

@Sarah: expectinginizia con il valore di array.length, che è quante richieste faremo. Sappiamo che il callback non verrà chiamato fino a quando tutte queste richieste non saranno avviate. Nel callback, procedi if (--expecting === 0)come segue: 1. Decrementi expecting(abbiamo ricevuto una risposta, quindi ci aspettiamo una risposta in meno) e se il valore dopo il decremento è 0 (non ci aspettiamo altre risposte), siamo fatto!
TJ Crowder,

1
@PatrickRoberts - Grazie !! Sì, errore di copia e incolla, quel secondo argomento è stato completamente ignorato in quell'esempio (che è l'unica ragione per cui non ha fallito, poiché come hai sottolineato, resultsnon esisteva). :-) Aggiustato.
TJ Crowder,

111

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 quando ritorna res.data.value). Quindi aspetti fino al completamento della richiesta $ http.get e quindi viene eseguita 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);
  });
})();

107

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

Quindi, se stai usando Angular, React o qualsiasi altro framework che fa due modi di legare i dati o archiviare il concetto, questo problema è semplicemente risolto per te, quindi in parole semplici, il tuo risultato è undefinedal primo stadio, quindi hai ottenuto result = undefinedprima di ricevere il dati, quindi non appena ottieni il risultato, verrà aggiornato e assegnato al nuovo valore quale risposta della tua chiamata Ajax ...

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

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

Ad esempio nel tuo caso che stai utilizzando 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 promesse e osservabili che sono modi più recenti per fare questo asincrono.


Questo va bene in ambito globale, ma in alcuni contesti di modulo probabilmente vorrai garantire il giusto contesto per il callback, ad es.$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims

8
Questo in realtà non è corretto poiché React è un'associazione di dati a senso unico
Matthew Brent,

@MatthewBrent non ti sbagli, ma non hai ragione, i puntelli React sono oggetti e, se cambiati, cambiano durante l'applicazione, ma non è un modo in cui gli sviluppatori React raccomandano di usarlo ...
Alireza,

98

È un problema molto comune che affrontiamo mentre lottiamo con i "misteri" di JavaScript. Vorrei 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 del previsto.

Ora aggiungiamo un po 'di torsione, 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

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 1s per restituire "wohoo". Ma come puoi vedere, il valore assegnato a bar è il materiale immediatamente restituito da foo (), che è niente, cioè solo undefined.

Quindi, come affrontiamo questo problema?

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

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

AGGIORNAMENTO (Promesse con asincrono / attendi)

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 chiarire le cose, puoi trovare un confronto qui sotto:

quindi / prendere la versione:

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

versione asincrona / wait:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }

è ancora considerato il modo migliore per restituire un valore da una promessa o asincrono / attendi?
edwardsmarkf,

3
@edwardsmarkf Personalmente non penso che esista un modo migliore in quanto tale. Uso promesse con then / catch, async / wait e generatori per parti asincrone del mio codice. Dipende in gran parte dal contesto di utilizzo.
Anish K.,

96

Un altro approccio per restituire un valore da una funzione asincrona consiste nel passare 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 il result oggetto per memorizzare il valore durante l'operazione asincrona. Ciò consente al risultato di essere disponibile anche dopo il lavoro asincrono.

Uso questo approccio molto. Sarei interessato a sapere come funziona questo approccio in cui è coinvolto il cablaggio del risultato attraverso moduli consecutivi.


9
Non c'è niente di speciale nell'uso di un oggetto qui. Funzionerebbe anche se gli assegnassi la risposta direttamente result. Funziona perché stai leggendo la variabile al termine della funzione asincrona.
Felix Kling,

85

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

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

Finiresti per passare async1; controlla se namenon è definito o no e chiama il callback di conseguenza.

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

async1(name, async2)

Mentre va bene in piccoli esempi, diventa fastidioso quando hai molti casi simili e 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 .


1
@recurf - Non è il mio progetto. Puoi provare a utilizzare il loro tracker dei problemi.
Rohithpr,

1
è simile alle funzioni del generatore? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… *
Emanegux

1
È ancora rilevante?
Aluan Haddad,

È possibile utilizzare async-awaitse si stanno utilizzando alcune delle versioni più recenti del nodo. Se qualcuno è bloccato con versioni precedenti, può utilizzare questo metodo.
rohithpr,

83

Il seguente esempio che ho scritto mostra come

  • Gestire le chiamate HTTP asincrone;
  • Attendere la risposta da ogni chiamata API;
  • Usa modello Promessa ;
  • Utilizzare il modello Promise.all per unire più chiamate HTTP;

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

Contesto. L'esempio sta eseguendo una query sull'endpoint dell'API Web Spotify 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 elemento, una nuova Promessa genererà un blocco - ExecutionBlock, analizza il risultato, pianifica una nuova serie di promesse in base alla matrice dei risultati, ovvero un elenco di useroggetti Spotify ed esegue la nuova chiamata HTTP in ExecutionProfileBlockmodo asincrono.

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

NOTA Le searchAPI Spotify recenti richiederanno un token di accesso da specificare nelle intestazioni della richiesta:

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

Pertanto, per eseguire il seguente esempio è 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 ampiamente discusso questa soluzione qui .


80

La risposta breve è che devi implementare un callback come questo:

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

$.ajax({
    url: "...",
    success: callback
});

78

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

Questo è abbastanza semplice:

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

Ecco una versione funzionante del tuo codice:

(async function(){

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

})()

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


7
Sfortunatamente, questo funziona solo con funzioni che restituiscono promesse, ad esempio non funziona con l'API Node.js, che utilizza i callback. E non consiglierei di usarlo senza Babel, perché non tutti usano "browser attuali".
Michał Perłakowski l'

2
@ Il nodo 8 di MichałPerłakowski include nodejs.org/api/util.html#util_util_promisify_original che può essere utilizzato per fare le promesse di ritorno dell'API node.js. Se hai il tempo e i soldi per supportare i browser non attuali dipende ovviamente dalla tua situazione.
Mikemaccana,

IE 11 è ancora un browser attuale nel 2018, purtroppo e non supportaawait/async
Juan Mendes,

IE11 non è un browser attuale. È stato rilasciato 5 anni fa, ha una quota di mercato mondiale del 2,5% secondo caniuse e, a meno che qualcuno non stia raddoppiando il budget per ignorare tutta la tecnologia attuale, non vale la pena per la maggior parte delle persone.
mikemaccana,

76

Js è un thread singolo.

Il browser può essere diviso in tre parti:

1) Loop degli eventi

2) API Web

3) Coda eventi

Event Loop funziona per sempre, vale a dire una specie di loop infinito. Event Queue è il punto in cui tutte le tue funzioni vengono spinte su qualche evento (esempio: click), questo è uno per uno portato fuori dalla coda e messo in Loop Event che esegue questa funzione e si prepara da solo per quello successivo dopo che viene eseguita la prima. Ciò significa che l'esecuzione di una funzione non inizia fino a quando la funzione prima che sia in coda non viene eseguita nel ciclo di eventi.

Ora pensiamo di aver inserito due funzioni in una coda, una è per ottenere dati dal server e un'altra li utilizza. Abbiamo prima inserito la funzione serverRequest () nella coda e poi 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 richiederà del tempo e quindi siamo impegnati nel nostro loop degli eventi in modo da appendere la nostra pagina, ecco dove il Web L'API entra in ruolo, prende questa funzione dal ciclo degli eventi e si occupa del server rendendo libero il ciclo degli eventi in modo da poter eseguire la funzione successiva 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 prossima funzione continuano fino alla fine della coda (questo si chiama chiamata asincrona, cioè possiamo fare qualcos'altro finché non otteniamo i dati)

Supponiamo che la nostra funzione serverRequest () avesse un'istruzione return in un codice, quando recuperiamo i dati dall'API Web del server li inseriremo in coda alla fine della coda. Dato che viene messo alla fine in coda non possiamo utilizzare i suoi dati in quanto non è rimasta alcuna funzione nella nostra 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 della richiamata ... Diamo la nostra funzione (funzione che utilizza i dati restituiti dal server) alla funzione che chiama il server.

Richiama

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

Callback Javscript.info


68

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

67

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

Se viene promessa la funzione sottostante

nsynjs valuterà tutte le promesse in sequenza e inserirà il risultato della 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 callback nel wrapper compatibile con nsynjs (se ha una versione promisificata, è possibile 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. Attivazione della logica sincrona:

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

Passaggio 3. Eseguire 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, mettendo in pausa l'esecuzione nel caso in cui il risultato di una funzione lenta non sia pronto.

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


2
Questo è interessante. Mi piace come consente di codificare le chiamate asincrone nel modo in cui lo faresti in altre lingue. Ma tecnicamente non è un vero JavaScript?
J Morris,

41

ECMAScript 6 ha "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 riportato, procedere come segue:

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

Se è necessario targetizzare i 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 un array e distrutti quando li leggi in modo che il modello possa far fronte a callback con più argomenti. Ad esempio con il nodo fs :

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

39

Ecco alcuni approcci per lavorare con richieste asincrone:

  1. Browser Promise object
  2. D - Una libreria 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: l'implementazione differita di jQuery per funzionare 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

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

I sistemi informatici che costruiamo - sempre di più - hanno il tempo come dimensione importante. Alcune cose sono programmate per accadere in futuro. Quindi devono accadere altre cose dopo che alla fine si verificano quelle prime cose. Questa è l'idea di base chiamata "asincronicità". Nel nostro mondo sempre più in rete, il caso più comune di asincronicità è in attesa che un sistema remoto risponda ad una 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, 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 si tradurrà in latte fino a qualche tempo futuro. JS e altri linguaggi dichiarativi eseguono un'istruzione dopo l'altra senza attendere.

L'approccio classico di JS a questo problema, sfruttando il fatto che JS supporta funzioni come oggetti di prima classe che possono essere passati, è quello di passare una funzione come parametro alla richiesta asincrona, che invocherà quindi al termine il suo compito in futuro. Questo è l'approccio "callback". Sembra così:

order_milk(put_in_coffee);

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

Il problema con questo approccio di callback è che inquina la semantica normale di una funzione con cui riporta il suo risultato 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 affrontano sequenze di eventi più lunghe. Ad esempio, diciamo che voglio aspettare che il latte venga messo nel caffè, e quindi e solo allora eseguire un terzo passo, vale a dire bere il caffè. Alla fine ho bisogno di scrivere qualcosa del genere:

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

dove sto passando put_in_coffeesia al latte per inserirlo, sia all'azione ( drink_coffee) da eseguire una volta inserito il latte. Tale codice diventa difficile da scrivere, leggere e 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 l'idea 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 accadrà in futuro, o potrebbe non accadere affatto. Le promesse hanno un solo metodo, chiamato then, al quale si passa un'azione da eseguire quando il risultato rappresentato dalla promessa è 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 ciò è che possiamo unirli insieme per creare sequenze di eventi futuri ("concatenamento"):

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

Facciamo 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 returnalla chiamata a $.ajax. Questo funziona perché jQuery $.ajaxrestituisce già una sorta di cosa promettente. (In pratica, senza entrare nei dettagli, preferiremmo racchiudere questa chiamata in modo da restituire una vera promessa, o utilizzare qualche alternativa a $.ajaxtale scopo.) Ora, se vogliamo caricare il file e attendere 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 utilizziamo le promesse, finiamo per passare molte funzioni then, quindi è spesso utile utilizzare le funzioni delle frecce 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 abbastanza 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 attendere il termine della prima chiamata prima di eseguire la seconda". Non sarebbe bello se ci fosse un modo per dirlo a JS? Si scopre che c'è - la awaitparola chiave, utilizzata all'interno di un tipo speciale di funzione chiamata funzione "asincrona". Questa funzione fa parte della prossima versione di ES, ma è già disponibile nei transpilers come Babel con i preset giusti. 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, saresti in grado di scrivere qualcosa del genere

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

37

Risposta breve : il foo()metodo ritorna immediatamente, mentre la $ajax()chiamata viene eseguita in modo asincrono dopo il ritorno della funzione . Il problema è quindi come o dove archiviare 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 archiviare i risultati in un membro di tale 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 in result.response.


14
Mentre funziona, non è molto meglio che assegnarlo a una variabile globale.
Felix Kling,

36

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

29

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à quella di evitare callback e utilizzare una combinazione di promesse e asincrono / attendi .

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

Consente prima di tutto l'utilizzo di JQuery, di essere $disponibile come S:

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

Definire una funzione che restituisce 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")

29

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

uso

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 'fino a quando non ho capito che c'è un errore nei vecchi browser:

Uncaught ReferenceError: Promise is not defined

Così ho deciso di implementare la mia classe Promise per ES3 su compilatori js inferiori se non definito. Basta aggiungere questo codice prima del codice principale e quindi utilizzare in modo sicuro Promessa!

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

28

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, 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.
}

5
Non c'è nulla di intrinsecamente asincrono nei callback o JavaScript.
Aluan Haddad,

19

Invece di lanciarti codice, ci sono 2 concetti che sono fondamentali per capire come JS gestisce callback e asincronicità. (è anche una parola?)

Il ciclo degli eventi e il modello di concorrenza

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

In termini ampi e semplicistici, il loop degli eventi è come il project manager, è costantemente in 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 esecuzione (come la tua richiesta AJAX). immaginalo così:

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

Quando uno di questi messaggi verrà eseguito, il messaggio viene rimosso dalla coda e crea uno stack, lo stack è tutto ciò che JS deve eseguire per eseguire l'istruzione nel messaggio. Quindi nel nostro esempio 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 loop degli eventi passerà alla cosa successiva nella coda (o ascolterà i messaggi)

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

QUANDO qualcosa funzionerà

Quando si effettua una chiamata utilizzando AJAX a una parte esterna o si esegue un codice asincrono (ad esempio un setTimeout), Javascript dipende da una risposta prima che possa procedere.

La grande domanda è quando riceverà la risposta? La risposta è che non lo sappiamo, quindi il loop degli eventi sta aspettando che quel messaggio dica "hey run me". Se JS avesse semplicemente atteso quel messaggio in modo sincrono, la tua app si sarebbe bloccata e avrebbe funzionato. Quindi JS continua a eseguire l'elemento successivo nella coda mentre attende che il messaggio venga aggiunto alla coda.

Ecco perché con la funzionalità asincrona utilizziamo cose chiamate callback . È quasi una promessa letteralmente. Come in, prometto di restituire qualcosa a un certo punto jQuery utilizza callback specifici chiamati deffered.done deffered.faile deffered.always(tra gli altri). Puoi vederli tutti qui

Quindi, ciò che devi fare è passare una funzione che viene promessa di eseguire a un certo punto con i dati che le vengono passati.

Poiché 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()

Spero che abbia un senso. Quando incontri cose del genere che sembrano confuse, ti consiglio vivamente di leggere la documentazione per capire almeno. Ti renderà uno sviluppatore molto migliore.


18

Utilizzando ES2017 dovresti avere questo come dichiarazione di funzione

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

E eseguirlo in questo modo.

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

O la sintassi Promise

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

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

})

potrebbe quella seconda funzione riutilizzabile ??
Zum Dummi,

Come si utilizzano i risultati se viene chiamato oncolse, log? A quel punto tutto non va alla console?
Ken Ingram,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.