Domanda Come posso restituire la risposta da una chiamata asincrona?


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

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

4303
2018-01-08 17:06


origine


risposte:


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

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

Il problema

Il UN  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. Nel tuo esempio, $.ajax ritorna immediatamente e la prossima dichiarazione, return result;, viene eseguito prima della funzione che hai passato come success il callback è stato persino chiamato.

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

Sincrono

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

Nonostante findItem potrebbe richiedere molto tempo per l'esecuzione, qualsiasi codice verrà dopo var item = findItem(); deve aspettare  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 lui dovrebbe ti richiamo  sul tuo cellulare Riattacca, esci di casa e fai qualunque cosa tu abbia pianificato di fare. Una volta che il tuo amico ti ha richiamato, hai a che fare con le informazioni che ti ha dato.

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

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

Invece di attendere la risposta, l'esecuzione continua immediatamente e viene eseguita la dichiarazione dopo la chiamata Ajax. Per ottenere la risposta alla fine, si fornisce una funzione da chiamare una volta ricevuta la risposta, a richiama  (notare qualcosa? richiama  ?). 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)
  • Promette con then() (ES2015 +, disponibile nei browser più vecchi se si utilizza una delle tante librerie di promessa)

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


ES2017 +: promette con async/await

Presentata la nuova versione di ECMAScript pubblicata nel 2017 supporto a livello di sintassi  per funzioni asincrone. Con l'aiuto di async e await, puoi scrivere in modo asincrono in uno "stile sincrono". Non fare alcun errore però: il codice è ancora asincrono, ma è più facile da leggere / capire.

async/await costruisce in cima alle promesse: a async la funzione restituisce sempre una promessa. await "scartare" una promessa e portare al valore con cui è stata risolta la promessa o gettare un errore se la promessa è stata respinta.

Importante:  Puoi solo usarlo await dentro un async funzione. Ciò significa che al livello più alto, devi ancora lavorare direttamente con la promessa.

Puoi leggere di più 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 a second (just for the sake of this example)
    await delay(1000);
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

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

Più nuovo del browser  e nodo  supporto di versioni async/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 Babele ).


Lascia accettare le funzioni 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 fare foo accettare una richiamata e usarla come success richiama. 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 lo chiamiamo e semplicemente lo passiamo a success. Cioè una volta che la richiesta Ajax ha avuto successo, $.ajax chiamerà callback e passare la risposta al callback (a cui si può fare riferimento result, poiché è così che abbiamo definito il callback).

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

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

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


ES2015 +: promette con poi()

Il Promise API  è una nuova funzionalità di ECMAScript 6 (ES2015), ma ha buone funzionalità supporto del browser  già. 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 futuro  valori. Quando la promessa riceve il valore (lo è risoluto ) o quando è annullato ( respinto ), notifica a tutti i suoi "ascoltatori" che desiderano accedere a questo valore.

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

Ecco un semplice esempio di utilizzo di una promessa:

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

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

Applicato alla nostra chiamata Ajax potremmo usare promesse come questa:

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

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

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

Ulteriori informazioni sulle promesse: Rocce HTML5 - Promesse JavaScript

Nota a margine: oggetti differiti di jQuery

Oggetti differiti  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 giusti contenitori  per un valore futuro, non sono il valore stesso. Ad esempio, supponi di avere il seguente:

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

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

Questo codice fraintende i suddetti problemi di asincronia. In particolare, $.ajax() non congela il codice mentre controlla la pagina '/ password' sul tuo server - invia una richiesta al server e, mentre attende, restituisce immediatamente un oggetto differito Ajax jQuery, non la risposta dal server. Ciò significa il if la dichiarazione otterrà sempre questo oggetto rinviato, trattandolo come truee procedere come se l'utente abbia effettuato l'accesso. Non buono.

Ma la soluzione è semplice:

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

Non consigliato: chiamate sincrone "Ajax"

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

Senza jQuery

Se usi direttamente a XMLHTTPRequest oggetto, passare false come terzo argomento a .open.

jQuery

Se usi jQuery , puoi impostare il async opzione a false. Si noti che questa opzione è deprecato  da quando jQuery 1.8. È quindi possibile utilizzare ancora a success richiamata o accesso al responseText proprietà del oggetto jqXHR :

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

Se si utilizza qualsiasi altro metodo jQuery Ajax, ad esempio $.get, $.getJSON, ecc., devi cambiarlo $.ajax(dal momento che è possibile solo passare i parametri di configurazione a $.ajax).

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


4653
2018-01-08 17:06



Se tu sei non  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 utilizzano jQuery per AJAX, ho deciso di fornire un'alternativa per le persone che non lo sono.

( Nota, per chi usa il nuovo fetch API, Angular o promesse 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.

Il UN  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. Nel tuo esempio, .send ritorna immediatamente e la prossima dichiarazione, return result;, viene eseguito prima della funzione che hai passato come success il callback è stato persino chiamato.

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

(Violino)

Il valore di a è tornato undefined dal momento che a=5 parte non è ancora stata eseguita. AJAX agisce 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 ri-attivamente  , dicendo al tuo programma cosa fare quando il calcolo è stato completato.

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 termina, stiamo dicendo al nostro codice come reagire quando un evento si completa (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

Ci sono fondamentalmente due modi per risolvere questo:

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

1. AJAX sincrono - Non farlo !!

Per quanto riguarda AJAX sincrono, non farlo!  La risposta di Felix solleva alcuni argomenti convincenti sul 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 tu avere  per farlo, puoi passare una bandiera: Ecco come:

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

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

2. Codice di ristrutturazione

Lascia che la tua funzione accetti una richiamata. Nel codice di esempio foo può essere fatto per accettare una richiamata. 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 ulteriori 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 fatto in modo che la nostra funzione foo accettasse un'azione da eseguire quando l'AJAX si completava correttamente, potremmo estendere 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 guida introduttiva di AJAX  a MDN.


885
2018-05-29 23:30



XMLHttpRequest  2  (prima di tutto leggi le risposte di Benjamin Gruenbaum e 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 tu 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.

Check-out XMLHttpRequest funzionalità avanzate

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.

Usando il XMLHttpRequest nome variabile è 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 di forma completa ( 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 degli errori

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

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

Nello script precedente, 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 davvero un errore il solo  modo è quello di 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 del 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 l'errore.

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

C'è solo il this.type che è impostato su 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 ... ??? Così 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 'di tempo carica un bel file txt 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 delle tue liste o qualsiasi altra 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 hai bisogno di una sola funzione. In tal caso, modificare leggermente la funzione Ajax e sostituire b con la funzione speciale.


Le funzioni di cui 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, popolato database websql con base64images e molto altro ... Ma in questi casi è necessario creare una funzione solo per questo 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).


301
2017-08-19 08:06



Se usi le promesse, questa risposta è per te.

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

Il tuo codice dovrebbe essere qualcosa del genere:

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

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

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


Il problema principale

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

Ogni volta che chiami un metodo che restituisce una promessa, il then gli handler sono sempre  eseguito in modo asincrono - cioè, dopo  il codice sotto di loro che non è in a .then handler.

Questo significa che stai tornando data il then il gestore che 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 è undefined dal momento che data = 5 parte non è ancora stata eseguita. Probabilmente verrà eseguito in un secondo, ma a quel punto è irrilevante per il valore restituito.

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

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

Ricapitolazione rapida sulle promesse

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

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

Una promessa può solo cambiare stato una volta  dopo di che rimarrà sempre nello stesso stato per sempre. Puoi allegare then gestori di promesse per estrarre il loro valore e gestire gli errori. then i gestori consentono concatenamento  di chiamate. Le promesse sono create da utilizzando le API che li restituiscono . Ad esempio, la più moderna sostituzione AJAX fetch o di jQuery $.get restituire le promesse.

Quando chiamiamo .then su una promessa e ritorno  qualcosa da questo - abbiamo una promessa per il valore elaborato . Se restituiremo un'altra promessa otterremo cose incredibili, ma teniamo i nostri cavalli.

Con promesse

Vediamo come possiamo risolvere il problema sopra con promesse. Per prima cosa, dimostriamo la nostra comprensione degli stati di promessa dall'alto usando il Prometti costruttore  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 then per renderlo conto:

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 a valore  che non possiamo fare a causa del modello di concorrenza - stiamo restituendo a involucro  per un valore che possiamo scartare  con then. È come una scatola con cui puoi aprire then.

Applicando questo

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

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

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

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

ES2015 (ES6)

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

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

È una funzione che restituisce un iteratore  oltre la sequenza 1,2,3,3,3,3,.... che può essere iterato. 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 futuro  valori - 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ò Bluebird Promise.coroutine qui, ma ci sono altri wrapper come co o Q.async.

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

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

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

ES2016 (ES7)

In ES7, questo è ulteriormente standardizzato, ci sono diverse proposte in questo momento, ma in ognuna di esse è possibile await promettere. Questo è solo "zucchero" (sintassi più bella) per la proposta ES6 di cui sopra aggiungendo il async e await parole chiave. 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 :)


243
2018-05-12 02:22



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.


192
2018-05-23 02:05



La soluzione più semplice è creare una funzione JavaScript e chiamarla per l'Ajax success richiama.

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

184
2018-02-18 18:58



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

enter image description here


154
2017-08-11 14:17



Angular1

Per le persone che stanno usando 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 sotto.

  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 con un'occhiata al seguente esempio, ma suo consigliato  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));
}

Vedere il originale  pubblicare qui Ma Typescript non supporta promesse native es6 , se vuoi usarlo, potresti aver bisogno di un plugin per questo.

Inoltre ecco le promesse spec  definire qui.


113
2017-08-26 08:11