javascript tutorial Come posso restituire la risposta da una chiamata asincrona?




javascript await async tutorial (24)

Ecco alcuni approcci per lavorare con le richieste asincrone:

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

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

var App = App || {};

App = {
    getDataFromServer: function(){

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

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

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

    init: function(){

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

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

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

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

function foo() {
    var result;

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

    return result;
}

var result = foo(); // It always ends up being `undefined`.

La maggior parte delle risposte qui fornisce suggerimenti utili per quando si ha una singola operazione asincrona, ma a volte, ciò si verifica quando è necessario eseguire un'operazione asincrona per ogni voce 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 doSomethingAsyncnon sono ancora stati eseguiti nel momento in cui stai cercando 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 del numero di callback che ti aspetti, 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 farla finita expectinge usarla results.length === theArray.length, ma questo ci lascia aperti alla possibilità che theArrayvenga cambiata mentre le chiamate sono in sospeso ...)

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

Ma cosa succede se è necessario restituire quei risultati da una funzione? Come hanno sottolineato le altre risposte, non puoi; devi accettare la funzione e chiamare una richiamata (o restituire una then() ). Ecco una versione di callback:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

Esempio:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Oppure ecco una versione che restituisce Promiseinvece:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Ovviamente, se doSomethingAsyncci passassero degli errori, utilizzeremmo rejectper rifiutare la promessa quando abbiamo 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 doSomethingAsyncche restituisce una promessa, e poi fai il seguente ...)

Se doSomethingAsyncti dà una then() , puoi usare Promise.all:

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

Esempio:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry, function(result) {
            results.push(result);
        });
    }));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

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

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 ogni 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 facendo il lavoro in serie, possiamo usarlo solo results.push(result)perché sappiamo che non otterremo risultati in ordine. In quanto sopra abbiamo potuto usare results[index] = result;, ma in alcuni dei seguenti esempi non abbiamo un indice usare.)

Esempio:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Oppure, ancora una volta, costruisci un wrapper doSomethingAsyncche ti dia una promessa e faccia il seguente ...)

Se doSomethingAsyncti dà una Promessa, se puoi usare la sintassi ES2017 + (magari con un transpiler come Babel ), puoi usare una async/await con for-ofe await:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Esempio:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Se non è possibile utilizzare la sintassi ES2017 + (ancora), è possibile utilizzare una variante del pattern "Promise reduce" (è più complesso del solito Promise riduci perché non stiamo passando il risultato da uno a quello successivo, ma invece raccogliendo i loro risultati in un array):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Esempio:

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 le funzioni freccia ES2015 + :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Esempio:

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


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

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

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

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

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

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

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

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

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


Angular1

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

Here dice,

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

Puoi trovare una bella spiegazione anche here .

Esempio trovato nei docs menzionati di seguito.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 e successivi

Con Angular2un'occhiata al seguente esempio, ma è recommended l'uso Observablescon Angular2.

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

}

Puoi consumarlo in questo modo

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

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

Inoltre ecco qui le spec promises definite qui.


Se usi le promesse, questa risposta è per te.

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

Il tuo codice dovrebbe essere qualcosa del genere:

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

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

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

Il problema principale

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

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

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

Ecco una semplice analogia per il problema:

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

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

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

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

Ricapitolazione rapida sulle promesse

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

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

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

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

Con promesse

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

function delay(ms){ // takes amount of milliseconds
    // returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // when the time is up
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

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

function delay(ms){ // takes amount of milliseconds
  // returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // when the time is up
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // we're RETURNING the promise, remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // when the promise is ready
      return 5; // return the value 5, promises are all about return values
  })
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){ 
   document.body.innerHTML = five;
});

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

Applicando questo

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

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

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

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

ES2015 (ES6)

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

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

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

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

Questo trucco un po 'complicato ma molto potente ci consente di scrivere codice asincrono in modo sincrono. Ci sono diversi "corridori" che fanno questo per te, scrivendo uno è un breve poche righe di codice ma è oltre lo scopo di questa risposta. Userò Bluebird Promise.coroutinequi, ma ci sono altri wrapper come coo Q.async.

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

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

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

ES2016 (ES7)

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

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

Ritorna comunque una promessa lo stesso :)


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

Se la funzione sottostante è promessa

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

function synchronousCode() {

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

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

Se la funzione sottostante non è promessa

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

var ajaxGet = function (ctx,url) {
    var res = {};
    var ex;
    $.ajax(url)
    .done(function (data) {
        res.data = data;
    })
    .fail(function(e) {
        ex = e;
    })
    .always(function() {
        ctx.resume(ex);
    });
    return res;
};
ajaxGet.nsynjsHasCallback = true;

Passaggio 2. Mettere in funzione la logica sincrona:

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

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

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

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

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


Js è un singolo threaded.

Il browser può essere diviso in tre parti:

1) Event Loop

2) API Web

3) Coda eventi

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

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

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

Quindi la soluzione a questo è callback o promessa .

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

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

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


  }
  xmlHttpReq.send(null);

}

Nel mio codice è chiamato come

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

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


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

Scopri le funzionalità 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'utilizzo del nome della variabile XMLHttpRequest è un altro grosso errore in quanto è necessario eseguire la richiamata all'interno delle chiusure onload / oreadystatechange che è stata persa.

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 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 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 veramente un errore l' unico modo è 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 si trova all'interno di displayAjax() in 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 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 ... ??? 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).


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

Ecco un esempio dello stesso:

var async = require("async");

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

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

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

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


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

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


Dai un'occhiata a questo esempio:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

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

Questo è il plnkr:

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


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)

})

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.


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

Il ciclo degli eventi e il modello di concorrenza

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

In termini ampi e semplicistici, il 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 chiamarefoobarFunc

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

Quindi tutto ciò che foobarFunc deve eseguire (nel nostro caso anotherFunction) verrà messo in pila. 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. Questo è

QUANDO sta andando a finire qualcosa

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 otterrà la risposta? La risposta è che non 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 in coda mentre attende che il messaggio venga aggiunto nuovamente alla coda.

Ecco perché con la funzionalità asincrona usiamo le cose chiamate callback . È un po 'come una then() letteralmente. Come in I prometto di restituire qualcosa ad un certo punto jQuery usa callback specifici chiamati deffered.done deffered.faile 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 del tempo (ma non sempre) si passa foononfoo()

Speriamo che abbia un senso. Quando incontri cose del genere che sembrano confuse - consiglio vivamente di leggere completamente la documentazione per averne almeno una comprensione. Ti renderà uno sviluppatore molto migliore.


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

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Per eseguire il codice sopra, fai questo:

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

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

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

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

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

function foo() {
    var result;

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

    return result;
}

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

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

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

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

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


Se non stai 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 coloro che utilizzano la nuova API di fetch , Angular o promesse che ho aggiunto un'altra risposta di seguito )

Cosa stai affrontando

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

L' 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, .send restituisce immediatamente e l'istruzione successiva, return result; , viene eseguito prima che la funzione passata fosse chiamata anche callback di success .

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 reso undefined è undefined poiché la parte a=5 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 in modo reattivo , dicendo al tuo programma cosa fare quando il calcolo sarà 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, passiamo a getFive un'azione da eseguire al termine, 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. (Fiddle) .

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 l'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 devi 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. Nell'esempio il codice foo può essere fatto per accettare un callback. Diremo il nostro codice su come reagire al completamento di foo .

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

(fiddle)

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 su MDN.


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

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 è avere prima il 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 alcuna delle tue richiamate, comprese le richieste XHR, impostare i timeout, i gestori di eventi dom, ecc. Ciascuno di questi callback in attesa di esecuzione si posiziona in una coda, in attesa il loro turno di essere eseguito 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. Dovresti prima di tutto come vuoi organizzare il tuo codice come essenzialmente la risposta a diversi eventi discreti e come vuoi raggrupparli in una sequenza logica. Puoi usare promesse o nuovi asincroni di livello più alto / attendere come strumenti a tal fine, oppure puoi lanciare 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.


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

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

            successCallback(response);
        }
    });
}

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

function foo(callback) {

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

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

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

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

Il problema

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

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

Sincrono

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

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

asincrono

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

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

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

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

Soluzione (s)

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

Perché è male chiedi?

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

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

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

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

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

ES2017 +: promette con async/await

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

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

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

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

Ecco un esempio che si basa sul ritardo sopra riportato:

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

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


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

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

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

Consenti alle funzioni di accettare i callback

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

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

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

diventa

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

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

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

foo(myCallback);

foo stesso è definito come segue:

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

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

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

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

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

ES2015 +: promette con then()

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

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

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

Ecco un semplice esempio di utilizzo di una promessa:

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

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

Applicato alla nostra chiamata Ajax potremmo usare promesse come questa:

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

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

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

Ulteriori informazioni sulle promesse: rocce HTML5 - Promesse JavaScript

Nota a margine: oggetti differiti di jQuery

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

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

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

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

Nota a margine: promemoria

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

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

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

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

Ma la soluzione è semplice:

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

Non consigliato: chiamate sincrone "Ajax"

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

Senza jQuery

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

jQuery

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

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

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

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


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

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

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

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

async1(name, async2)

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

Fibers aiuta a risolvere il problema.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Puoi controllare il progetto here .





ecmascript-2017