it-swarm.it

Come posso restituire la risposta da una chiamata asincrona?

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

Ho provato a restituire il valore dal callback success e ad assegnare la risposta a una variabile locale all'interno della funzione e restituirlo, 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`.
4904
Felix Kling

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

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

Il problema

Il A in Ajax sta per asincrono . Ciò significa che l'invio della richiesta (o piuttosto la ricezione della risposta) viene portato fuori dal normale flusso di esecuzione. Nell'esempio, $.ajax ritorna immediatamente e l'istruzione successiva, return result;, viene eseguita prima che la funzione che hai passato come success sia stata richiamata.

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

Sincrono

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

Anche se findItem potrebbe richiedere molto tempo per essere eseguito, ogni codice che segue var item = findItem(); deve wait finché 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 deve richiamarti sul tuo cellulare. Riattacca, esci di casa e fai qualunque cosa tu abbia pianificato di fare. Una volta che il tuo amico ti ha richiamato, hai a che fare con le informazioni che ti ha dato.

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

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

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


Soluzione (s)

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

Perché è male chiedi?

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

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

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

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

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


ES2017 +: promette con async/await

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

async/await si basa su promesse: una funzione async restituisce sempre una promessa. await "annulla" una promessa e produce il valore con cui è stata risolta la promessa o genera un errore se la promessa è stata respinta.

Importante: È possibile utilizzare await all'interno di una funzione async. Al momento, await di livello superiore non è ancora supportato, quindi potrebbe essere necessario creare un IIFE asincrono ( Immediately Invoked Function Expression ) per avviare un contesto async.

Puoi leggere ulteriori informazioni su async e await su MDN.

Ecco un esempio che si basa sul ritardo sopra riportato:

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

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


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

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

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


Consenti alle funzioni di accettare callback

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

Nell'esempio della domanda, puoi rendere foo accettare un callback e usarlo come success callback. Così questo

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

diventa

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

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

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

foo(myCallback);

foo stesso è definito come segue:

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

callback farà riferimento alla funzione che passiamo a foo quando la chiamiamo e semplicemente la passiamo a success. Cioè una volta che la richiesta Ajax ha avuto successo, $.ajax chiamerà callback e 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 alla richiamata:

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

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


ES2015 +: promette con then ()

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

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

Il vantaggio rispetto alle callback semplici è che consentono di separare il codice e sono più facili da comporre.

Ecco un semplice esempio di utilizzo di una promessa:

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

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

Applicato alla nostra chiamata Ajax potremmo usare promesse come questa:

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

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

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

Ulteriori informazioni sulle promesse: HTML5 rocks - JavaScript Promises

Nota a margine: oggetti differiti di jQuery

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

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

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

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

Nota a margine: promemoria

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

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

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

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

Ma la soluzione è semplice:

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

Non consigliato: chiamate sincrone "Ajax"

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

Senza jQuery

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

jQuery

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

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

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

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

5203
Felix Kling

Se sei not usando jQuery nel tuo codice, questa risposta è per te

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 coloro che usano la nuova API fetch, Angular o promesse che ho aggiunto un'altra risposta qui sotto )


Cosa stai affrontando

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

IlAin AJAX sta per asincrono . Ciò significa che l'invio della richiesta (o piuttosto la ricezione della risposta) viene portato fuori dal normale flusso di esecuzione. Nell'esempio, .send restituisce immediatamente e l'istruzione successiva, return result;, viene eseguita prima che la funzione passata come success sia stata richiamata.

Questo significa che quando stai tornando, 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;
}

(Fiddle)

Il valore di a restituito è undefined poiché la parte a=5 non è stata ancora eseguita. AJAX si comporta in questo modo, restituisci il valore prima che il server abbia la possibilità di dire al tuo browser quale sia quel valore.

Una possibile soluzione a questo problema è codificare re-attivamente, indicando 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 è chiamato CPS . Fondamentalmente, stiamo passando getFive un'azione da eseguire quando si completa, stiamo dicendo al nostro codice come reagire quando un evento si completa (come la nostra AJAX chiamata, o in questo caso il timeout).

L'utilizzo sarebbe:

getFive(onComplete);

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

Possibili soluzioni

Ci sono fondamentalmente due modi per risolvere questo:

  1. Rendi AJAX chiamata 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é sia ​​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 da MDN sul perché:

XMLHttpRequest supporta sia le comunicazioni sincrone che 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 hai per farlo, puoi passare un flag: 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 il codice foo può essere fatto per accettare un callback. Diremo al nostro codice come reagire quando foo completa.

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

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

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

Ora, definiamo foo stesso ad 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 reso la nostra funzione foo accetta un'azione da eseguire quando il AJAX viene completato correttamente, possiamo estenderlo ulteriormente controllando se lo stato della risposta non è 200 e agendo di conseguenza (creare un gestore errori e così via). Risolvendo efficacemente il nostro problema.

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

1002

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

Se non usi jQuery e vuoi un bel 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 è impostata direttamente (quindi nessuna chiusura inutile).
  3. Usa il nuovo onload (quindi non devi controllare per readystate e stato)
  4. Ci sono altre situazioni che non ricordo che rendono XMLHttpRequest 1 fastidioso.

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 ragione bind() il callback a una classe:

e.target.response

Esempio:

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

O (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.

Controlla Caratteristiche avanzate di XMLHttpRequest

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

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

L'uso del nome della variabile XMLHttpRequest è un altro grosso errore in quanto è necessario eseguire la richiamata all'interno delle chiusure onload/oreadystatechange che 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 ottiene e posta.

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 a modulo completo (document.getElementsByTagName('form')[0]):

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

O 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 && synchronous interrompe completamente il punto della risposta. Quale è 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 precedente, si dispone di 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 veramente un errore il only way è scrivere un URL sbagliato nel qual caso ogni browser genera un errore.

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

Anche se si passa 'POSTAPAPAP' come metodo, non genera un errore.

Anche se si passa 'fdggdgilfdghfldj' come formdata, non genera un errore.

Nel primo caso l'errore è all'interno di displayAjax() sotto this.statusText come Method not Allowed.

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

il dominio incrociato non consentito genera automaticamente un errore.

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

C'è solo il this.type che è impostato sull'errore.

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

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

PS: Come primo test ho scritto x ('x', displayAjax) ..., e ha ottenuto una risposta totale ... ??? Quindi ho controllato la cartella in cui si trova l'HTML, e c'era un file chiamato "x.xml". Quindi, anche se si dimentica l'estensione del proprio file, XMLHttpRequest 2 LO TROVERA. Ho LOL'd


Leggi un file sincrono

Non farlo.

Se vuoi bloccare il browser per un po 'carica un bel file .txt Nice sincrono.

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

Ora puoi farlo

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

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

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

Solo se hai una pagina in cui carichi sempre lo stesso XML/JSON o qualsiasi altra cosa hai bisogno di una sola funzione. In tal caso, modificare leggermente la funzione Ajax e sostituire b con la funzione speciale.


Le funzioni sopra sono per l'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 GET solo ...

Ma puoi fare un sacco di cose con XMLHttpRequest 2:

Ho creato un gestore di download (utilizzando intervalli su entrambi i lati con curriculum, file manager, file system), vari convertitori di immagini con resizer utilizzando canvas, popola 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 le intestazioni, sovrascrivere il mimetype e c'è molto altro ...

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

363
cocco

Se usi le promesse, questa risposta è per te.

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

Il tuo codice dovrebbe essere qualcosa del genere:

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

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

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


Il problema principale

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

Ogni volta che chiami un metodo che restituisce una promessa, i gestori thensono sempreeseguiti in modo asincrono - cioè, dopo il codice sotto di loro che non è in un gestore .then.

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

Ecco una semplice analogia per il problema:

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

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

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

Una possibile soluzione a questo problema è la codifica re-active, che dice al programma cosa fare quando il calcolo è completato.) Promette attivamente di abilitare questo in natura (in termini temporali).

Ricapitolazione rapida sulle promesse

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

  • fulfilled che significa che il calcolo è stato completato con successo.
  • respinto significa che il calcolo non è riuscito.

Una promessa può cambiare solo gli stati una voltadopo di che rimarrà sempre nello stesso stato per sempre.È possibile associare i gestori di thenalle promesse per estrarne il valore e gestire gli errori.I gestori thenconsentono il concatenamento delle chiamate. sono creati da utilizzando API che li restituiscono . Ad esempio, il più moderno AJAX replacement fetcho jQuery's $.get restituisce promesse.

Quando chiamiamo .then su una promessa e returnqualcosa da esso - otteniamo una promessa per il valore processato. Se restituiamo un'altra promessa otterremo cose incredibili, ma teniamo i nostri cavalli.

Con promesse

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

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

Fondamentalmente, invece di restituire un valoreche non possiamo fare a causa del modello di concorrenza - stiamo restituendo un wrapperper un valore che possiamo unwrapcon thenname__. È come un casella che puoi aprire con thenname__.

Applicando questo

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

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

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

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

ES2015 (ES6)

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

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

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

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

Questo trucco un po 'complicato ma molto potente ci consente di scrivere codice asincrono in modo sincrono. Ci sono diversi "corridori" che fanno questo per te, scrivendo uno è un breve poche righe di codice ma è oltre lo scopo di questa risposta. Userò qui Promise.coroutine di Bluebird, 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, 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 si può promettere awaitname__. Questo è solo "zucchero" (sintassi più gradevole) per la proposta ES6 di cui sopra aggiungendo le parole chiave asynce awaitname__. Fare l'esempio sopra:

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

Ritorna comunque una promessa lo stesso :)

292

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

Questo è:

function handleData( responseData ) {

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

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

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

230
Nic

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

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

            successCallback(response);
        }
    });
}

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

function foo(callback) {

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

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

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

 enter image description here

195

Angular1

Per le persone che usano AngularJS , può gestire questa situazione usando Promises.

Qui dice,

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

Puoi trovare una bella spiegazione qui anche.

Esempio trovato in docs menzionato 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 successivi

In Angular2 guarda il seguente esempio, ma è raccomandato per usare Observables con 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 original post qui. Ma TypeScript non supporta nativo es6 Promises , se vuoi usarlo, potresti aver bisogno di un plugin per questo.

Inoltre ecco qui le promesse spec define.

143

La maggior parte delle risposte qui fornisce utili suggerimenti per quando si ha una singola operazione asincrona, ma a volte, ciò si verifica quando è necessario eseguire un'operazione asincrona per each entry in una matrice o altra struttura ad 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, si hanno 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 e quindi utilizzare i risultati quando hai ottenuto molti 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 eliminare expectinge usare solo results.length === theArray.length, ma questo ci lascia aperti alla possibilità che theArraysia cambiato mentre le chiamate sono in sospeso ...)

Notate come usiamo indexda forEachper salvare il risultato in resultsnella stessa posizione della voce a cui si riferisce, anche se i risultati arrivano fuori ordine (poiché le chiamate asincrone non necessariamente vengono completate nell'ordine in cui sono state avviate ).

Ma cosa succede se è necessario return i risultati di una funzione? Come hanno sottolineato le altre risposte, non puoi; devi accettare la funzione e chiamare un callback (o restituire un Promise ). 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 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 ha passato degli errori, dovremmo usare rejectper rifiutare la promessa quando abbiamo ricevuto 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 wrapper per doSomethingAsyncche restituisce una promessa, e poi fai il seguente ...)

Se doSomethingAsyncti dà un Promise , 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 il terzo argomento, puoi semplicemente passarlo direttamente a map(mapchiama la sua 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:

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

Si noti che Promise.all risolve la propria promessa con una serie di risultati di tutte le promesse che gli si danno quando sono tutte risolte, o rifiuta la sua promessa quando first delle promesse che gli si danno.

Serie

Supponiamo che tu non voglia che le operazioni siano in parallelo? Se si desidera eseguirli uno dopo l'altro, è necessario attendere il completamento di ciascuna operazione prima di avviare il successivo. 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 lavorando in serie, possiamo semplicemente usare results.Push(result) dato che sappiamo che non otterremo risultati in ordine. In quanto sopra, avremmo potuto usare results[index] = result;, ma in alcuni dei seguenti esempi non avere un indice da 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;
}

(Oppure, ancora una volta, crea un wrapper per doSomethingAsyncche ti dia una promessa e fai il seguente ...)

Se doSomethingAsyncti dà un Promise, se puoi usare la sintassi ES2017 + (magari con un transpiler come Babel ), puoi usare una asyncfunction con for-of e awaitNAME_ :

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 ES2017 + (ancora), puoi usare una variazione sul modello "Promise reduce" (questo è più complesso del solito Promise riduci perché non passiamo il risultato da uno a successivamente, 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:

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;
}
122
T.J. Crowder

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 getJoke is return a resolved promise (viene risolto quando restituisce res.data.value). Quindi aspettate che $ http.get request sia completato e quindi console.log (res.joke) viene eseguito (come un normale flusso asincrono).

Questo è il plnkr:

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

ES6 way (async - await)

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

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

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

Ecco un esempio dello stesso:

var async = require("async");

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

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

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

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

85
jsbisht

Questo è uno dei posti che bind dei due modi che è usato in molti nuovi framework JavaScript funzionerà molto per te ...

Quindi se stai usando Angular, React o qualsiasi altro framework che fa due modi di associazione dei dati, questo problema è semplicemente corretto per te, quindi in easy Word, il tuo risultato è undefined al prima fase, quindi hai result = undefined prima di ricevere i dati, quindi non appena ottieni il risultato, verrà aggiornato e verrà assegnato al nuovo valore che risponde alla tua chiamata Ajax ...

Ma come puoi farlo in pure javascript o jQuery per esempio come hai chiesto in questa domanda?

Puoi usare un callback , promise e recentemente observable per gestirlo per te, per esempio nelle promesse abbiamo alcune funzioni come success () o then () che verrà eseguito quando i dati sono pronti per te, lo stesso con callback o subscribe function on observable .

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

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

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

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

Per ulteriori informazioni, studia su promises e observables che sono i nuovi modi per fare questo async stuffs.

80
Alireza

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

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

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

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

async1(name, async2)

Mentre è okay in piccoli esempi diventa fastidioso quando si hanno molti casi simili e la gestione degli errori coinvolta.

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 .

77
rohithpr

Risposta breve è, 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
});
73

Il seguente esempio che ho scritto mostra come

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

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

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

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

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

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

NOTELe ultime API Spotify search richiedono un token di accesso da specificare nelle intestazioni delle richieste:

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

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

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

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

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

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

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

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

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

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

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

    ///// promiseall again

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

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

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

    /////

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

Ho discusso ampiamente di questa soluzione qui .

70
loretoparisi

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

Questo è abbastanza semplice:

  • Restituire una promessa
  • Usa 'await' , che dirà a JavaScript di attendere la promessa di essere risolto in un valore (come la risposta HTTP)
  • Aggiungi il 'async' keyword 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 correnti e nel nodo 8

67
mikemaccana

È possibile utilizzare questa libreria personalizzata (scritta utilizzando Promise) per effettuare una chiamata remota.

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

Semplice esempio di utilizzo:

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

Js è un singolo threaded.

Il browser può essere diviso in tre parti:

1) Event Loop

2) API Web

3) Coda eventi

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

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

Supponiamo che la nostra funzione serverRequest () abbia un'istruzione return in un codice, quando recuperiamo i dati dall'API Web del server lo inseriremo in coda alla fine della coda. Dato che alla fine viene spinto in coda, non possiamo utilizzare i suoi dati poiché non è rimasta alcuna funzione nella nostra coda per utilizzare questi dati. Quindi non è possibile restituire qualcosa dalla chiamata asincrona.

Quindi la soluzione a questo è callback o promise.

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

 CallBack

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

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


  }
  xmlHttpReq.send(null);

}

Nel mio codice è chiamato come

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

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

55
Aniket Jha

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

Se la funzione sottostante è promessa

nsynjs valuterà tutte le promesse in sequenza e metterà i risultati di promessa nella proprietà data:

function synchronousCode() {

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

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

Se la funzione sottostante non è stata promessa

Passaggio 1. Funzione di avvolgimento con callback nel wrapper nsynjs-aware (se ha una versione promessa, è 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. Mettere in funzione la logica sincrona:

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

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

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

Nsynjs valuterà passo per passo tutti gli operatori e le espressioni, interrompendo l'esecuzione nel caso in cui il risultato di alcune funzioni lente non sia pronto.

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

54
amaksr

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

Iniziamo con una semplice funzione JavaScript:

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

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

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

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

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

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

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

Quindi, come affrontiamo questo problema?

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

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

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

Quindi, il riassunto è - per affrontare le funzioni asincrone come le chiamate basate su un jax, ecc., Puoi usare una promessa per resolve il valore (che intendi restituire). Quindi, in breve si risolve valore invece di restituisce , nelle funzioni asincrone.

AGGIORNAMENTO (promette con async/await)

Oltre all'uso di then/catch per lavorare con le promesse, esiste un ulteriore approccio. L'idea è di riconoscere una funzione asincrona e quindi attendere le promesse per risolvere, prima di passare alla riga successiva del codice. È ancora solo il promises sotto il cofano, ma con un diverso approccio sintattico. Per rendere le cose più chiare, puoi trovare un confronto di seguito:

quindi/cattura la versione:

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

versione asincrona/attesa:

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

ECMAScript 6 ha 'generatori' che ti permettono 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 devi scegliere come target i browser che non supportano ES6, puoi eseguire il codice tramite Babel o closure-compiler per generare ECMAScript 5.

Il callback ...args è racchiuso in una matrice e destrutturato quando vengono letti in modo tale che il pattern possa far fronte a callback con più argomenti. Ad esempio con node fs :

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

Ecco alcuni approcci per lavorare con le richieste asincrone:

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

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

var App = App || {};

App = {
    getDataFromServer: function(){

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

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

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

    init: function(){

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

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

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

Diverse soluzioni sono state fornite in questo thread. Forse il modo più semplice è passare un oggetto al metodo foo() e memorizzare i risultati in un membro di quell'oggetto dopo che la chiamata asincronica è stata completata.

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

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

33
David R Tribble

Utilizzare una funzione callback() all'interno della funzione foo(). 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();
33
Mahfuzur Rahman

Ci troviamo in un universo che sembra progredire lungo una dimensione che chiamiamo "tempo". Non capiamo veramente che tempo sia, 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 una dimensione importante. Certe cose sono programmate per accadere in futuro. Poi altre cose devono accadere dopo che queste prime cose si sono verificate. Questa è la nozione base chiamata "asincronicità". Nel nostro mondo sempre più collegato in rete, il caso più comune di asincronismo è aspettare che qualche sistema remoto risponda ad alcune richieste.

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

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

Perché JS non ha modo di sapere che ha bisogno di wait per order_milk per finire prima di eseguire put_in_coffee. In altre parole, non sa che order_milk è asincrono - è qualcosa che non produrrà latte fino a qualche ora futura. JS e altri linguaggi dichiarativi eseguono una dichiarazione dopo l'altra senza attendere.

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

order_milk(put_in_coffee);

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

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

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

dove sto passando a put_in_coffee sia il latte da inserire, sia l'azione (drink_coffee) da eseguire una volta che il latte è stato inserito. Tale codice diventa difficile da scrivere, leggere e 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 promesse

Questa era la motivazione per la nozione di una "promessa", che è un particolare tipo di valore che rappresenta un futuro o asincrono risultato di qualche tipo. Può rappresentare qualcosa che è già accaduto, o che succederà in futuro, o potrebbe non accadere mai. Le promesse hanno un unico metodo, chiamato 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_milk per restituire una promessa per il latte in arrivo, quindi specificare put_in_coffee come un'azione then, come segue:

order_milk() . then(put_in_coffee)

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

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

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

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

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

get_data() . then(do_something)

per esempio,

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

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

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

La parola chiave async

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 è necessario wait che la prima chiamata finisca prima di eseguire il secondo". Non sarebbe bello se là fosse un modo per dirlo a JS? Si scopre che esiste la parola chiave await, utilizzata all'interno di un tipo speciale di funzione chiamata funzione "asincrona". Questa funzione fa parte della versione imminente di ES, ma è già disponibile in transpilers come Babel, dato i preset giusti. Questo ci permette semplicemente di scrivere

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

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

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

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

function foo() {
    var result;

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

    return result;
}

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

La domanda era:

Come posso restituire la risposta da una chiamata asincrona?

che può essere interpretato come:

Come rendere asincrono code look synchronous ?

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

Vorrei dare un esempio per una richiesta Ajax.

(Anche se può essere scritto in Javascript, preferisco scriverlo in Python e compilarlo in Javascript usando Transcrypt . Sarà abbastanza chiaro).

Prima abilita l'utilizzo di JQuery, per avere $ disponibile come S:

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

Definisci una funzione che restituisce un Promise , 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 asincrono codice 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")
24

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

})
14

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

Event Loop and Concurrency Model

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

In termini ampi e semplicistici, il ciclo degli eventi è come il project manager, è costantemente in ascolto di tutte le funzioni che vogliono essere eseguite e comunicano tra la coda e lo stack.

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

Una volta che riceve un messaggio per eseguire qualcosa, lo aggiunge alla coda. La coda è l'elenco di cose che sono in attesa di esecuzione (come la tua richiesta AJAX). immagina così:

 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, apre 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 viene detto di chiamare foobarFunc

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 degli eventi passerà quindi alla prossima cosa in coda (o ascolterà i messaggi)

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

WHEN sta andando a correre

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

La grande domanda è quando riceverà la risposta? La risposta è che non lo sappiamo - quindi il ciclo degli eventi è in attesa che quel messaggio dica "hey run me". Se JS aspettava solo quel messaggio in modo sincrono, l'app si bloccava e faceva schifo. Quindi, JS continua ad eseguire l'elemento successivo nella coda in attesa che il messaggio venga aggiunto nuovamente alla coda.

Ecco perché con la funzionalità asincrona usiamo le cose chiamate callbacks. È un po 'come un promise letteralmente. Come in I prometti di restituire qualcosa ad un certo punto jQuery usa richiami specifici chiamati deffered.donedeffered.fail e deffered.always (tra gli altri). Puoi vederli tutti here

Quindi quello che devi fare è passare una funzione che è stata promessa di eseguire ad un certo punto con i dati che gli vengono passati.

Perché una richiamata non viene eseguita 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 delle volte (ma non sempre) passerai foo not foo()

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

13
Matthew Brent

Vediamo prima la foresta prima di guardare gli alberi.

Ci sono molte risposte informative con grandi dettagli qui, non ripeterò nessuno di loro. La chiave per programmare in JavaScript è il primo modello mentale corretto dell'esecuzione complessiva.

  1. Il tuo punto di accesso (s) è eseguito come risultato di un evento. Ad esempio, un tag script con codice viene caricato nel browser. (Di conseguenza, questo è il motivo per cui potrebbe essere necessario preoccuparsi della prontezza della pagina per eseguire il codice se richiede che vengano prima costruiti gli elementi dom, ecc.)
  2. Il tuo codice viene eseguito fino al completamento - a prescindere dalle numerose chiamate asincrone - senza eseguire any dei tuoi callback, comprese le richieste XHR, impostare i timeout, i gestori di eventi dom, ecc. Ognuno di quei callback che attendono di essere eseguiti siederanno una coda, aspettando il loro turno per essere eseguita dopo che gli altri eventi che hanno sparato hanno finito l'esecuzione.
  3. Ogni singolo richiamo a una richiesta XHR, imposta il timeout o dom l'evento una volta richiamato verrà quindi eseguito fino al completamento.

La buona notizia è che se capisci bene questo punto, non dovrai mai preoccuparti delle condizioni di gara. In primo luogo, devi innanzitutto organizzare il tuo codice come essenzialmente la risposta a diversi eventi discreti e come desideri raggrupparli in una sequenza logica. Puoi usare promesse o nuovi asincroni di livello superiore/attendere come strumenti a tal fine, oppure puoi fare il tuo.

Ma non dovresti usare alcuno strumento tattico per risolvere un problema finché non ti senti a tuo agio con il vero dominio del problema. Disegna una mappa di queste dipendenze per sapere cosa deve essere eseguito quando. Il tentativo di un approccio ad hoc a tutte queste callback non ti servirà al meglio.

13
Haim Zamir

Usando Promessa

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 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 per i compilatori below js se non è definita. Basta aggiungere questo codice prima del codice principale e poi Promessa utente sicurezza!

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

Ecco un esempio che funziona:

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

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