Wie gebe ich die Antwort von einem asynchronen Aufruf zurück?

5511

Ich habe eine Funktion foo, die eine Ajax-Anfrage stellt. Wie kann ich die Antwort von zurückgeben foo?

Ich habe versucht, den Wert aus dem successRückruf zurückzugeben, die Antwort einer lokalen Variablen innerhalb der Funktion zuzuweisen und diese zurückzugeben, aber keine dieser Möglichkeiten gibt die Antwort tatsächlich zurück.

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`.
Felix Kling
quelle

Antworten:

5703

→ Eine allgemeinere Erklärung des asynchronen Verhaltens anhand verschiedener Beispiele finden Sie unter Warum bleibt meine Variable unverändert, nachdem ich sie innerhalb einer Funktion geändert habe? - Asynchrone Code-Referenz

→ Wenn Sie das Problem bereits verstanden haben, fahren Sie mit den folgenden möglichen Lösungen fort.

Das Problem

Das A in Ajax steht für asynchron . Das bedeutet, dass das Senden der Anforderung (oder vielmehr das Empfangen der Antwort) aus dem normalen Ausführungsfluss herausgenommen wird. In Ihrem Beispiel wird $.ajaxsofort zurückgegeben und die nächste Anweisung return result;wird ausgeführt, bevor die Funktion, die Sie als successRückruf übergeben haben, überhaupt aufgerufen wurde.

Hier ist eine Analogie, die hoffentlich den Unterschied zwischen synchronem und asynchronem Fluss klarer macht:

Synchron

Stellen Sie sich vor, Sie telefonieren mit einem Freund und bitten ihn, etwas für Sie nachzuschlagen. Obwohl es eine Weile dauern kann, warten Sie am Telefon und starren in den Weltraum, bis Ihr Freund Ihnen die Antwort gibt, die Sie benötigen.

Das gleiche passiert, wenn Sie einen Funktionsaufruf mit "normalem" Code ausführen:

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

var item = findItem();

// Do something with item
doSomethingElse();

Auch wenn findItemeine lange Zeit ausführen dauern kann, kommt jeder Code nach var item = findItem();muss warten , bis die Funktion das Ergebnis zurück.

Asynchron

Sie rufen Ihren Freund aus dem gleichen Grund erneut an. Aber diesmal sagst du ihm, dass du es eilig hast und er dich auf deinem Handy zurückrufen sollte. Sie legen auf, verlassen das Haus und tun, was Sie vorhatten. Sobald Ihr Freund Sie zurückruft, haben Sie es mit den Informationen zu tun, die er Ihnen gegeben hat.

Genau das passiert, wenn Sie eine Ajax-Anfrage stellen.

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

Anstatt auf die Antwort zu warten, wird die Ausführung sofort fortgesetzt und die Anweisung nach dem Ajax-Aufruf ausgeführt. Um die Antwort schließlich zu erhalten, geben Sie eine Funktion an, die aufgerufen werden kann, sobald die Antwort empfangen wurde, einen Rückruf (beachten Sie etwas? Rückruf ?). Jede Anweisung, die nach diesem Aufruf kommt, wird ausgeführt, bevor der Rückruf aufgerufen wird.


Lösung (en)

Umfassen Sie die asynchrone Natur von JavaScript! Während bestimmte asynchrone Operationen synchrone Gegenstücke bereitstellen (ebenso wie "Ajax"), wird generell davon abgeraten, diese zu verwenden, insbesondere in einem Browserkontext.

Warum ist es schlecht, fragst du?

JavaScript wird im UI-Thread des Browsers ausgeführt, und bei einem lang laufenden Prozess wird die Benutzeroberfläche gesperrt, sodass sie nicht mehr reagiert. Darüber hinaus gibt es eine Obergrenze für die Ausführungszeit von JavaScript, und der Browser fragt den Benutzer, ob die Ausführung fortgesetzt werden soll oder nicht.

All dies ist eine wirklich schlechte Benutzererfahrung. Der Benutzer kann nicht feststellen, ob alles einwandfrei funktioniert oder nicht. Darüber hinaus ist der Effekt für Benutzer mit einer langsamen Verbindung schlechter.

Im Folgenden werden drei verschiedene Lösungen betrachtet, die alle aufeinander aufbauen:

  • Versprechen mitasync/await (ES2017 +, verfügbar in älteren Browsern, wenn Sie einen Transpiler oder Regenerator verwenden)
  • Rückrufe (beliebt im Knoten)
  • Versprechen mitthen() (ES2015 +, verfügbar in älteren Browsern, wenn Sie eine der vielen Versprechen-Bibliotheken verwenden)

Alle drei sind in aktuellen Browsern und Knoten 7+ verfügbar.


ES2017 +: Versprechen mit async/await

Mit der 2017 veröffentlichten ECMAScript-Version wurde die Unterstützung für asynchrone Funktionen auf Syntaxebene eingeführt. Mit Hilfe von asyncund awaitkönnen Sie asynchron in einem "synchronen Stil" schreiben. Der Code ist immer noch asynchron, aber leichter zu lesen / verstehen.

async/awaitbaut auf Versprechungen auf: Eine asyncFunktion gibt immer ein Versprechen zurück. await"packt" ein Versprechen aus und führt entweder zu dem Wert, mit dem das Versprechen gelöst wurde, oder wirft einen Fehler aus, wenn das Versprechen abgelehnt wurde.

Wichtig: Sie können nur awaitinnerhalb einer asyncFunktion verwenden. awaitDerzeit wird die oberste Ebene noch nicht unterstützt. Daher müssen Sie möglicherweise ein asynchrones IIFE ( sofort aufgerufener Funktionsausdruck ) erstellen , um einen asyncKontext zu starten .

Sie können mehr über asyncund awaitauf MDN lesen .

Hier ist ein Beispiel, das auf der obigen Verzögerung aufbaut:

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

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


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

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

Aktuelle Browser- und Knotenversionen unterstützen async/await. Sie können auch ältere Umgebungen unterstützen, indem Sie Ihren Code mithilfe von Regenerator (oder Tools, die Regenerator verwenden, wie z. B. Babel ) in ES5 umwandeln .


Lassen Sie Funktionen Rückrufe akzeptieren

Ein Rückruf ist einfach eine Funktion, die an eine andere Funktion übergeben wird. Diese andere Funktion kann die übergebene Funktion aufrufen, wann immer sie bereit ist. Im Kontext eines asynchronen Prozesses wird der Rückruf immer dann aufgerufen, wenn der asynchrone Prozess ausgeführt wird. Normalerweise wird das Ergebnis an den Rückruf übergeben.

Im Beispiel der Frage können Sie fooeinen Rückruf annehmen und als successRückruf verwenden. Also das

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

wird

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

Hier haben wir die Funktion "inline" definiert, aber Sie können jede Funktionsreferenz übergeben:

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

foo(myCallback);

foo selbst ist wie folgt definiert:

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

callbackwird sich auf die Funktion beziehen, an die wir übergeben, foowenn wir sie aufrufen, und wir geben sie einfach an weiter success. Dh wenn die Ajax-Anfrage erfolgreich ist, $.ajaxwird callbackdie Antwort aufgerufen und an den Rückruf weitergeleitet (auf den verwiesen werden kann result, da wir den Rückruf auf diese Weise definiert haben).

Sie können die Antwort auch verarbeiten, bevor Sie sie an den Rückruf weiterleiten:

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

Es ist einfacher, Code mithilfe von Rückrufen zu schreiben, als es scheint. Schließlich ist JavaScript im Browser stark ereignisgesteuert (DOM-Ereignisse). Das Empfangen der Ajax-Antwort ist nichts anderes als ein Ereignis.
Wenn Sie mit Code von Drittanbietern arbeiten müssen, können Schwierigkeiten auftreten. Die meisten Probleme können jedoch gelöst werden, indem Sie nur den Anwendungsfluss durchdenken.


ES2015 +: Verspricht mit then ()

Die Promise-API ist eine neue Funktion von ECMAScript 6 (ES2015), bietet jedoch bereits eine gute Browserunterstützung . Es gibt auch viele Bibliotheken, die die Standard-Promises-API implementieren und zusätzliche Methoden bereitstellen, um die Verwendung und Zusammensetzung von asynchronen Funktionen (z . B. Bluebird ) zu vereinfachen .

Versprechen sind Container für zukünftige Werte. Wenn das Versprechen den Wert erhält (es wird aufgelöst ) oder wenn es storniert ( abgelehnt ) wird, benachrichtigt es alle "Listener", die auf diesen Wert zugreifen möchten.

Der Vorteil gegenüber einfachen Rückrufen besteht darin, dass Sie Ihren Code entkoppeln können und einfacher zu erstellen sind.

Hier ist ein einfaches Beispiel für die Verwendung eines Versprechens:

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

Auf unseren Ajax-Aufruf angewendet könnten wir solche Versprechen verwenden:

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

Die Beschreibung aller Vorteile, die das Versprechen bietet, würde den Rahmen dieser Antwort sprengen. Wenn Sie jedoch neuen Code schreiben, sollten Sie diese ernsthaft in Betracht ziehen. Sie bieten eine großartige Abstraktion und Trennung Ihres Codes.

Weitere Informationen zu Versprechungen: HTML5 rockt - JavaScript-Versprechungen

Randnotiz: Zurückgestellte Objekte von jQuery

Zurückgestellte Objekte sind die benutzerdefinierte Implementierung von Versprechen durch jQuery (bevor die Promise-API standardisiert wurde). Sie verhalten sich fast wie Versprechen, zeigen jedoch eine etwas andere API.

Jede Ajax-Methode von jQuery gibt bereits ein "zurückgestelltes Objekt" zurück (eigentlich ein Versprechen eines zurückgestellten Objekts), das Sie einfach von Ihrer Funktion zurückgeben können:

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

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

Randnotiz: Versprechen Fallstricke

Denken Sie daran, dass Versprechen und zurückgestellte Objekte nur Container für einen zukünftigen Wert sind, sie sind nicht der Wert selbst. Angenommen, Sie hatten Folgendes:

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
}

Dieser Code versteht die oben genannten Asynchronitätsprobleme falsch. Insbesondere wird $.ajax()der Code nicht eingefroren, während die Seite '/ password' auf Ihrem Server überprüft wird. Er sendet eine Anforderung an den Server und gibt während des Wartens sofort ein jQuery Ajax Deferred-Objekt zurück, nicht die Antwort vom Server. Das bedeutet, dass die ifAnweisung dieses verzögerte Objekt immer abruft, als behandelt trueund so fortfährt, als wäre der Benutzer angemeldet. Nicht gut.

Aber die Lösung ist einfach:

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

Nicht empfohlen: Synchrone "Ajax" -Aufrufe

Wie bereits erwähnt, haben einige (!) Asynchrone Operationen synchrone Gegenstücke. Ich befürworte ihre Verwendung nicht, aber der Vollständigkeit halber würden Sie hier einen synchronen Anruf ausführen:

Ohne jQuery

Wenn Sie ein XMLHTTPRequestObjekt direkt verwenden , übergeben Sie es falseals drittes Argument an .open.

jQuery

Wenn Sie jQuery verwenden , können Sie die asyncOption auf setzen false. Beachten Sie, dass diese Option seit jQuery 1.8 veraltet ist. Sie können dann entweder noch einen successRückruf verwenden oder auf die responseTextEigenschaft des jqXHR-Objekts zugreifen :

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

Wenn Sie eine andere jQuery Ajax - Methode verwenden, wie zum Beispiel $.get, $.getJSONusw., müssen Sie es ändern $.ajax(da Sie nur Konfigurationsparameter passieren können $.ajax).

Kopf hoch! Es ist nicht möglich, eine synchrone JSONP- Anfrage zu stellen. JSONP ist von Natur aus immer asynchron (ein weiterer Grund, diese Option nicht einmal in Betracht zu ziehen).

Felix Kling
quelle
74
@Pommy: Wenn Sie jQuery verwenden möchten, müssen Sie es einschließen. Weitere Informationen finden Sie unter docs.jquery.com/Tutorials:Getting_Started_with_jQuery .
Felix Kling
11
In Lösung 1, sub jQuery, konnte ich diese Zeile nicht verstehen: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Ja, mir ist klar, dass mein Nick in diesem Fall ein bisschen ironisch ist)
cssyphus
32
@ Gibberish: Mmmh, ich weiß nicht, wie es klarer gemacht werden kann. Sehen Sie, wie fooaufgerufen wird und eine Funktion an ihn übergeben wird ( foo(function(result) {....});)? resultwird in dieser Funktion verwendet und ist die Antwort der Ajax-Anfrage. Um auf diese Funktion zu verweisen, wird anstelle einer anonymen Funktion der erste Parameter von foo aufgerufen callbackund zugewiesen success. Also, $.ajaxwird anrufen, callbackwenn die Anfrage erfolgreich war. Ich habe versucht, es ein bisschen mehr zu erklären.
Felix Kling
43
Der Chat für diese Frage ist tot, daher bin ich mir nicht sicher, wo ich skizzierte Änderungen vorschlagen soll, aber ich schlage vor: 1) Ändern Sie den synchronen Teil in eine einfache Diskussion darüber, warum es schlecht ist, ohne ein Codebeispiel dafür. 2) Entfernen / Zusammenführen der Rückrufbeispiele, um nur den flexibleren verzögerten Ansatz zu zeigen, der meiner Meinung nach auch für diejenigen, die Javascript lernen, etwas einfacher zu befolgen ist.
Chris Moschini
14
@ Jessi: Ich denke, Sie haben diesen Teil der Antwort falsch verstanden. Sie können nicht verwenden, $.getJSONwenn die Ajax-Anforderung synchron sein soll. Sie sollten jedoch nicht möchten, dass die Anforderung synchron ist, damit dies nicht zutrifft. Sie sollten Rückrufe oder Versprechen verwenden, um die Antwort zu verarbeiten, wie weiter oben in der Antwort erläutert.
Felix Kling
1071

Wenn Sie nicht jQuery in Ihrem Code verwenden, ist diese Antwort für Sie

Ihr Code sollte ungefähr so ​​aussehen:

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 hat gute Arbeit geleistet und eine Antwort für Leute geschrieben, die jQuery für AJAX verwenden. Ich habe beschlossen, eine Alternative für Leute bereitzustellen, die es nicht sind.

( Hinweis: Für diejenigen, die die neue fetchAPI, Angular oder Versprechen verwenden, habe ich unten eine weitere Antwort hinzugefügt. )


Was du vor dir hast

Dies ist eine kurze Zusammenfassung von "Erklärung des Problems" aus der anderen Antwort. Wenn Sie sich nach dem Lesen nicht sicher sind, lesen Sie diese.

Das A in AJAX steht für asynchron . Das bedeutet, dass das Senden der Anforderung (oder vielmehr das Empfangen der Antwort) aus dem normalen Ausführungsfluss herausgenommen wird. In Ihrem Beispiel wird .sendsofort zurückgegeben und die nächste Anweisung return result;wird ausgeführt, bevor die Funktion, die Sie als successRückruf übergeben haben, überhaupt aufgerufen wurde.

Dies bedeutet, dass der von Ihnen definierte Listener bei Ihrer Rückkehr noch nicht ausgeführt wurde. Dies bedeutet, dass der von Ihnen zurückgegebene Wert nicht definiert wurde.

Hier ist eine einfache Analogie

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

(Geige)

Der Wert von areturn ist, undefinedda das a=5Teil noch nicht ausgeführt wurde. AJAX verhält sich so: Sie geben den Wert zurück, bevor der Server Ihrem Browser mitteilen kann, um welchen Wert es sich handelt.

Eine mögliche Lösung für dieses Problem ist es , Code wieder aktiv , erzählen Sie Ihr Programm , was zu tun , wenn die Berechnung abgeschlossen.

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

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

Dies wird als CPS bezeichnet . Grundsätzlich übergeben wir getFiveeine Aktion, die ausgeführt werden soll, wenn sie abgeschlossen ist. Wir teilen unserem Code mit, wie er reagieren soll, wenn ein Ereignis abgeschlossen ist (wie unser AJAX-Aufruf oder in diesem Fall das Timeout).

Verwendung wäre:

getFive(onComplete);

Welches sollte "5" auf dem Bildschirm alarmieren. (Geige) .

Mögliche Lösungen

Grundsätzlich gibt es zwei Möglichkeiten, dies zu lösen:

  1. Machen Sie den AJAX-Aufruf synchron (nennen wir ihn SJAX).
  2. Restrukturieren Sie Ihren Code so, dass er ordnungsgemäß mit Rückrufen funktioniert.

1. Synchroner AJAX - Tu es nicht !!

Was synchrones AJAX betrifft, tun Sie es nicht! Felix 'Antwort wirft einige überzeugende Argumente dafür auf, warum es eine schlechte Idee ist. Zusammenfassend lässt sich sagen, dass der Browser des Benutzers eingefroren wird, bis der Server die Antwort zurückgibt und eine sehr schlechte Benutzererfahrung erzeugt. Hier ist eine weitere kurze Zusammenfassung von MDN, warum:

XMLHttpRequest unterstützt sowohl synchrone als auch asynchrone Kommunikation. Im Allgemeinen sollten jedoch aus Leistungsgründen asynchrone Anforderungen synchronen Anforderungen vorgezogen werden.

Kurz gesagt, synchrone Anforderungen blockieren die Ausführung von Code ... ... dies kann schwerwiegende Probleme verursachen ...

Wenn Sie es tun müssen, können Sie eine Flagge übergeben: So geht's:

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. Code umstrukturieren

Lassen Sie Ihre Funktion einen Rückruf annehmen. Im Beispiel fookann Code verwendet werden, um einen Rückruf anzunehmen. Wir werden unseren Code erzählen , wie man reagieren , wenn fooabgeschlossen ist .

Damit:

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

Wird:

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

Hier haben wir eine anonyme Funktion übergeben, aber wir könnten genauso gut einen Verweis auf eine vorhandene Funktion übergeben, sodass es so aussieht:

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

Weitere Informationen dazu, wie diese Art von Rückrufdesign ausgeführt wird, finden Sie in Felix 'Antwort.

Definieren wir nun foo selbst, um entsprechend zu handeln

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

(Geige)

Wir haben jetzt unsere foo-Funktion dazu gebracht, eine Aktion zu akzeptieren, die ausgeführt werden soll, wenn der AJAX erfolgreich abgeschlossen wurde. Wir können dies weiter erweitern, indem wir prüfen, ob der Antwortstatus nicht 200 ist, und entsprechend handeln (einen Fail-Handler erstellen und so weiter). Wir lösen unser Problem effektiv.

Wenn Sie immer noch Schwierigkeiten haben, dies zu verstehen, lesen Sie den AJAX-Leitfaden für die ersten Schritte bei MDN.

Benjamin Gruenbaum
quelle
20
"Synchrone Anforderungen blockieren die Ausführung von Code und können Speicher und Ereignisse verlieren" Wie kann eine synchrone Anforderung Speicher verlieren?
Matthew G
10
@MatthewG Ich habe in dieser Frage ein Kopfgeld hinzugefügt , ich werde sehen, was ich herausfischen kann. In der Zwischenzeit entferne ich das Zitat aus der Antwort.
Benjamin Gruenbaum
17
Nur als Referenz erlaubt XHR 2 die Verwendung des onloadHandlers, der nur dann ausgelöst wird, wenn dies der Fall readyStateist 4. Natürlich wird es in IE8 nicht unterstützt. (iirc, muss möglicherweise bestätigt werden.)
Florian Margaine
9
Ihre Erklärung, wie eine anonyme Funktion als Rückruf übergeben wird, ist gültig, aber irreführend. Das Beispiel var bar = foo (); fordert die Definition einer Variablen an, während Ihr vorgeschlagenes foo (functim () {}); definiert keine Bar
Robbie Averill
396

XMLHttpRequest 2 (lesen Sie zuerst die Antworten von Benjamin Gruenbaum & Felix Kling )

Wenn Sie jQuery nicht verwenden und eine schöne kurze XMLHttpRequest 2 möchten, die auf den modernen Browsern und auch auf den mobilen Browsern funktioniert, empfehle ich, sie folgendermaßen zu verwenden:

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

Wie du siehst:

  1. Es ist kürzer als alle anderen aufgelisteten Funktionen.
  2. Der Rückruf wird direkt eingestellt (also keine zusätzlichen unnötigen Schließungen).
  3. Es verwendet die neue Onload (sodass Sie nicht nach dem Readystate &&-Status suchen müssen).
  4. Es gibt einige andere Situationen, an die ich mich nicht erinnere, die XMLHttpRequest 1 nerven.

Es gibt zwei Möglichkeiten, um die Antwort auf diesen Ajax-Aufruf zu erhalten (drei mit dem Variablennamen XMLHttpRequest):

Das einfachste:

this.response

Oder wenn Sie aus irgendeinem Grund bind()den Rückruf zu einer Klasse:

e.target.response

Beispiel:

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

Oder (die obige ist besser, anonyme Funktionen sind immer ein Problem):

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

Nichts einfacher.

Jetzt werden einige Leute wahrscheinlich sagen, dass es besser ist, onreadystatechange oder sogar den Variablennamen XMLHttpRequest zu verwenden. Das ist falsch.

Schauen Sie sich die erweiterten Funktionen von XMLHttpRequest an

Es werden alle * modernen Browser unterstützt. Und ich kann bestätigen, dass ich diesen Ansatz verwende, da XMLHttpRequest 2 vorhanden ist. Ich hatte nie Probleme mit allen von mir verwendeten Browsern.

onreadystatechange ist nur nützlich, wenn Sie die Header in Status 2 erhalten möchten.

Die Verwendung des XMLHttpRequestVariablennamens ist ein weiterer großer Fehler, da Sie den Rückruf innerhalb der Onload / oreadystatechange-Verschlüsse ausführen müssen, sonst haben Sie ihn verloren.


Wenn Sie nun mit post und FormData etwas Komplexeres möchten, können Sie diese Funktion ganz einfach erweitern:

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

Wieder ... es ist eine sehr kurze Funktion, aber es wird & postet.

Anwendungsbeispiele:

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

Oder übergeben Sie ein vollständiges Formularelement ( document.getElementsByTagName('form')[0]):

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

Oder legen Sie einige benutzerdefinierte Werte fest:

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

Wie Sie sehen, habe ich die Synchronisierung nicht implementiert ... es ist eine schlechte Sache.

Trotzdem ... warum nicht einfach?


Wie im Kommentar erwähnt, bricht die Verwendung von error && synchronous den Punkt der Antwort vollständig. Welches ist eine schöne kurze Möglichkeit, Ajax richtig einzusetzen?

Fehlerbehandlungsroutine

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

Im obigen Skript haben Sie eine Fehlerbehandlungsroutine, die statisch definiert ist, damit die Funktion nicht beeinträchtigt wird. Der Fehlerbehandler kann auch für andere Funktionen verwendet werden.

Um einen Fehler wirklich zu beheben, müssen Sie nur eine falsche URL schreiben. In diesem Fall gibt jeder Browser einen Fehler aus.

Fehlerbehandlungsroutinen sind möglicherweise nützlich, wenn Sie benutzerdefinierte Header festlegen, den responseType auf Blob-Array-Puffer setzen oder was auch immer ...

Selbst wenn Sie 'POSTAPAPAP' als Methode übergeben, wird kein Fehler ausgegeben.

Selbst wenn Sie 'fdggdgilfdghfldj' als Formdaten übergeben, wird kein Fehler ausgegeben.

Im ersten Fall ist der Fehler in der displayAjax()unter this.statusTextwie Method not Allowed.

Im zweiten Fall funktioniert es einfach. Sie müssen auf der Serverseite prüfen, ob Sie die richtigen Postdaten übergeben haben.

domänenübergreifend nicht erlaubt löst automatisch einen Fehler aus.

In der Fehlerantwort gibt es keine Fehlercodes.

Es gibt nur das, this.typewas auf Fehler gesetzt ist.

Warum einen Fehlerbehandler hinzufügen, wenn Sie keine Kontrolle über Fehler haben? Die meisten Fehler werden in der Rückruffunktion zurückgegeben displayAjax().

Also: Keine Notwendigkeit für Fehlerprüfungen, wenn Sie die URL richtig kopieren und einfügen können. ;)

PS: Als ersten Test habe ich x ('x', displayAjax) geschrieben ... und es hat total eine Antwort bekommen ... ??? Also habe ich den Ordner überprüft, in dem sich der HTML-Code befindet, und es gab eine Datei namens 'x.xml'. Selbst wenn Sie die Erweiterung Ihrer Datei vergessen, wird XMLHttpRequest 2 sie finden . Ich habe laut gelacht


Lesen Sie eine Datei synchron

Tu das nicht.

Wenn Sie den Browser für eine Weile blockieren möchten, laden Sie eine schöne große .txtDatei synchron.

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

Jetzt können Sie tun

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

Es gibt keine andere Möglichkeit, dies nicht asynchron zu tun. (Ja, mit setTimeout-Schleife ... aber im Ernst?)

Ein weiterer Punkt ist ... wenn Sie mit APIs oder nur den Dateien Ihrer eigenen Liste arbeiten oder was auch immer Sie immer unterschiedliche Funktionen für jede Anfrage verwenden ...

Nur wenn Sie eine Seite haben, auf der Sie immer das gleiche XML / JSON laden oder was auch immer Sie nur eine Funktion benötigen. Ändern Sie in diesem Fall die Ajax-Funktion ein wenig und ersetzen Sie b durch Ihre spezielle Funktion.


Die oben genannten Funktionen dienen der grundlegenden Verwendung.

Wenn Sie die Funktion erweitern möchten ...

Ja, du kannst.

Ich verwende viele APIs und eine der ersten Funktionen, die ich in jede HTML-Seite integriere, ist die erste Ajax-Funktion in dieser Antwort, nur mit GET ...

Mit XMLHttpRequest 2 können Sie jedoch eine Menge Dinge tun:

Ich habe einen Download-Manager (mit Bereichen auf beiden Seiten mit Resume, Filereader, Dateisystem) erstellt, verschiedene Image Resizer-Konverter mit Canvas, Web-SQL-Datenbanken mit base64images und vielem mehr ... Aber in diesen Fällen sollten Sie nur dafür eine Funktion erstellen Zweck ... manchmal benötigen Sie einen Blob, Array-Puffer, Sie können Header setzen, Mimetyp überschreiben und es gibt noch viel mehr ...

Aber die Frage hier ist, wie man eine Ajax-Antwort zurückgibt ... (Ich habe einen einfachen Weg hinzugefügt.)

cocco
quelle
15
Diese Antwort ist zwar nett (und wir alle lieben XHR2 und das Posten von Dateidaten und mehrteiligen Daten ist absolut fantastisch) - dies zeigt syntaktischen Zucker für das Posten von XHR mit JavaScript - Sie möchten dies vielleicht in einen Blog-Beitrag einfügen (ich würde es mögen) oder sogar in einer Bibliothek (nicht sicher über den Namen x, ajaxoder xhrkönnte schöner sein :)). Ich sehe nicht, wie es die Rückgabe der Antwort von einem AJAX-Anruf adressiert. (Jemand könnte es immer noch tun var res = x("url")und nicht verstehen, warum es nicht funktioniert;)). Nebenbei bemerkt - es wäre cool, wenn Sie cvon der Methode zurückkehren würden, damit Benutzer sich anschließen können errorusw.
Benjamin Gruenbaum
25
2.ajax is meant to be async.. so NO var res=x('url')..Das ist der ganze Punkt dieser Frage und Antworten :)
Benjamin Gruenbaum
3
Warum gibt es in den Funktionen einen 'c'-Parameter, wenn Sie in der ersten Zeile den Wert überschreiben, den er hatte? vermisse ich etwas
Brian H.
2
Sie können Parameter als Platzhalter verwenden, um zu vermeiden, dass mehrmals "var" geschrieben wird
cocco
11
@cocco Sie haben also irreführenden, unlesbaren Code in eine SO- Antwort geschrieben , um ein paar Tastenanschläge zu sparen? Bitte tu das nicht.
Stein
316

Wenn Sie Versprechen verwenden, ist diese Antwort für Sie.

Dies bedeutet AngularJS, jQuery (mit verzögertem Zugriff), das Ersetzen (Abrufen) von nativem XHR, EmberJS, das Speichern von BackboneJS oder jede Knotenbibliothek, die Versprechen zurückgibt.

Ihr Code sollte ungefähr so ​​aussehen:

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 hat gute Arbeit geleistet und eine Antwort für Leute geschrieben, die jQuery mit Rückrufen für AJAX verwenden. Ich habe eine Antwort für native XHR. Diese Antwort gilt für die generische Verwendung von Versprechungen im Frontend oder Backend.


Das Kernthema

Das JavaScript-Parallelitätsmodell im Browser und auf dem Server mit NodeJS / io.js ist asynchron und reaktiv .

Wenn Sie eine Methode aufrufen, die ein Versprechen zurückgibt, werden die thenHandler immer asynchron ausgeführt, dh nach dem Code unter ihnen, der sich nicht in einem .thenHandler befindet.

Dies bedeutet, dass datader von thenIhnen definierte Handler bei der Rückgabe noch nicht ausgeführt wurde. Dies bedeutet wiederum, dass der von Ihnen zurückgegebene Wert nicht rechtzeitig auf den richtigen Wert eingestellt wurde.

Hier ist eine einfache Analogie für das Problem:

    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

Der Wert von dataist, undefinedda das data = 5Teil noch nicht ausgeführt wurde. Es wird wahrscheinlich in einer Sekunde ausgeführt, ist aber zu diesem Zeitpunkt für den zurückgegebenen Wert irrelevant.

Da der Vorgang noch nicht ausgeführt wurde (AJAX, Serveraufruf, E / A, Timer), geben Sie den Wert zurück, bevor die Anforderung die Möglichkeit erhielt, Ihrem Code den Wert mitzuteilen.

Eine mögliche Lösung für dieses Problem ist es , Code wieder aktiv , erzählen Sie Ihr Programm , was zu tun , wenn die Berechnung abgeschlossen. Versprechen ermöglichen dies aktiv, indem sie zeitlicher (zeitkritischer) Natur sind.

Schnelle Zusammenfassung der Versprechen

Ein Versprechen ist ein Wert im Laufe der Zeit . Versprechen haben Status, sie beginnen als ausstehend ohne Wert und können sich mit Folgendem abfinden:

  • erfüllt bedeutet, dass die Berechnung erfolgreich abgeschlossen wurde.
  • abgelehnt, was bedeutet, dass die Berechnung fehlgeschlagen ist.

Ein Versprechen kann den Zustand nur einmal ändern, danach bleibt es für immer im selben Zustand. Sie können thenHandler an Versprechen anhängen , um deren Wert zu extrahieren und Fehler zu behandeln. thenHandler ermöglichen die Verkettung von Anrufen. Versprechen werden mithilfe von APIs erstellt, die sie zurückgeben . Zum Beispiel der modernere AJAX-Ersatz fetchoder die $.getRückkehr von jQuery .

Wenn wir rufen .thenauf ein Versprechen und Rückkehr etwas von ihm - wir ein Versprechen für bekommen den verarbeiteten Wert . Wenn wir ein weiteres Versprechen zurückgeben, werden wir erstaunliche Dinge bekommen, aber lassen Sie uns unsere Pferde halten.

Mit Versprechungen

Mal sehen, wie wir das oben genannte Problem mit Versprechungen lösen können. Lassen Sie uns zunächst unser Verständnis der Versprechen von oben demonstrieren, indem wir den Versprechen-Konstruktor zum Erstellen einer Verzögerungsfunktion verwenden:

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

Nachdem wir setTimeout so konvertiert haben, dass es Versprechen verwendet, können wir es verwenden then, damit es zählt:

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

Grundsätzlich statt einer Rückkehr Wert , den wir nicht wegen der Gleichzeitigkeit Modell tun - wir Rückkehr einen Wrapper für einen Wert , dass wir auspacken mit then. Es ist wie eine Schachtel, mit der man öffnen kann then.

Dies anwenden

Dies gilt auch für Ihren ursprünglichen API-Aufruf. Sie können:

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

Das funktioniert also genauso gut. Wir haben gelernt, dass wir keine Werte von bereits asynchronen Aufrufen zurückgeben können, aber wir können Versprechen verwenden und sie verketten, um die Verarbeitung durchzuführen. Wir wissen jetzt, wie die Antwort von einem asynchronen Aufruf zurückgegeben wird.

ES2015 (ES6)

ES6 führt Generatoren ein , Funktionen, die in der Mitte zurückkehren und dann den Punkt wieder aufnehmen können, an dem sie sich befanden. Dies ist normalerweise nützlich für Sequenzen, zum Beispiel:

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

Ist eine Funktion, die einen Iterator über die Sequenz zurückgibt 1,2,3,3,3,3,...., der iteriert werden kann. Während dies für sich genommen interessant ist und Raum für viele Möglichkeiten eröffnet, gibt es einen besonders interessanten Fall.

Wenn es sich bei der von uns erstellten Sequenz eher um eine Sequenz von Aktionen als um Zahlen handelt, können wir die Funktion anhalten, wenn eine Aktion ausgeführt wird, und darauf warten, bevor wir die Funktion fortsetzen. Anstelle einer Folge von Zahlen brauchen wir also eine Folge zukünftiger Werte - das heißt: Versprechen.

Mit diesem etwas kniffligen, aber sehr mächtigen Trick können wir asynchronen Code synchron schreiben. Es gibt mehrere "Läufer", die dies für Sie tun. Das Schreiben einer ist ein paar kurze Codezeilen, die jedoch den Rahmen dieser Antwort sprengen. Ich werde hier Bluebirds verwenden Promise.coroutine, aber es gibt andere Wrapper wie cooder 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
});

Diese Methode gibt selbst ein Versprechen zurück, das wir von anderen Coroutinen konsumieren können. Zum Beispiel:

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 ist dies weiter standardisiert, es gibt derzeit mehrere Vorschläge, aber in allen können Sie awaitversprechen. Dies ist nur "Zucker" (schönere Syntax) für den obigen ES6-Vorschlag durch Hinzufügen der Schlüsselwörter asyncund await. Machen Sie das obige Beispiel:

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
}

Es gibt trotzdem ein Versprechen zurück :)

Benjamin Gruenbaum
quelle
Dies sollte die akzeptierte Antwort sein. +1 für async / warten (obwohl sollten wir nicht return await data.json();?)
Lewis Donovan
247

Sie verwenden Ajax falsch. Die Idee ist, dass nichts zurückgegeben wird, sondern die Daten an eine sogenannte Rückruffunktion übergeben werden, die die Daten verarbeitet.

Das ist:

function handleData( responseData ) {

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

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

Wenn Sie etwas im Submit-Handler zurückgeben, wird nichts ausgeführt. Sie müssen stattdessen entweder die Daten übergeben oder direkt in der Erfolgsfunktion damit tun, was Sie wollen.

Nic
quelle
13
Diese Antwort ist vollständig semantisch ... Ihre Erfolgsmethode ist nur ein Rückruf innerhalb eines Rückrufs. Sie könnten einfach haben success: handleDataund es würde funktionieren.
Jacques
5
Und was ist, wenn Sie die "responseData" außerhalb von "handleData" zurückgeben möchten ... :) ... wie werden Sie das tun ...? ... weil eine einfache Rückkehr es zum "erfolgreichen" Rückruf des
Ajax zurückbringt
@Jacques & @pesho hristov Sie haben diesen Punkt verpasst. Der Submit-Handler ist nicht die successMethode, sondern der umgebende Bereich von $.ajax.
Travnik
@travnik Das habe ich nicht vermisst. Wenn Sie den Inhalt von handleData nehmen und in die Erfolgsmethode einfügen würden, würde es genauso funktionieren ...
Jacques
234

Die einfachste Lösung besteht darin, eine JavaScript-Funktion zu erstellen und für den Ajax- successRückruf aufzurufen .

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);    
}); 
Hemant Bavle
quelle
3
Ich weiß nicht, wer es negativ gewählt hat. Aber dies ist eine Umgehung, die tatsächlich funktioniert hat. Ich habe diesen Ansatz verwendet, um eine ganze Anwendung zu erstellen. Die jquery.ajax gibt keine Daten zurück, daher ist es besser, den obigen Ansatz zu verwenden. Wenn es falsch ist, erklären Sie bitte und schlagen Sie einen besseren Weg vor, dies zu tun.
Hemant Bavle
11
Entschuldigung, ich habe vergessen, einen Kommentar zu hinterlassen (normalerweise!). Ich habe es abgelehnt. Downvotes weisen nicht auf sachliche Richtigkeit oder Mangel an, sondern auf Nützlichkeit im Kontext oder Mangel an. Ich finde Ihre Antwort angesichts der von Felix nicht nützlich, was dies bereits ausführlicher erklärt. Nebenbei bemerkt, warum sollten Sie die Antwort als JSON kennzeichnen?
Benjamin Gruenbaum
5
ok .. @Benjamin Ich habe stringify verwendet, um ein JSON-Objekt in einen String zu konvertieren. Und danke, dass Sie Ihren Standpunkt klargestellt haben. Denken Sie daran, ausführlichere Antworten zu veröffentlichen.
Hemant Bavle
Und was ist, wenn Sie das "responseObj" außerhalb von "successCallback" zurückgeben möchten ... :) ... wie werden Sie es tun ...? ... weil eine einfache Rückkehr es zum "Erfolg" -Rückruf des
Ajax zurückbringt
221

Ich werde mit einem schrecklich aussehenden, handgezeichneten Comic antworten. Das zweite Bild ist der Grund , warum resultist undefinedin Ihrem Codebeispiel.

Geben Sie hier die Bildbeschreibung ein

Johannes Fahrenkrug
quelle
32
Ein Bild sagt mehr als tausend Worte . Person A - Bitten Sie die Person B um Details, um sein Auto zu reparieren. Person B - Ruft Ajax an und wartet auf die Antwort des Servers, um Details zur Reparatur des Autos zu erhalten. Wenn eine Antwort eingeht, ruft die Ajax Success-Funktion die Person an B-Funktion und übergibt die Antwort als Argument an sie, Person A erhält die Antwort.
Shaijut
10
Wäre großartig, wenn Sie jedem Bild Codezeilen hinzufügen würden, um die Konzepte zu veranschaulichen.
Hassan Baig
1
Währenddessen steckt der Typ mit dem Auto am Straßenrand fest. Er verlangt, dass das Auto repariert wird, bevor er fortfährt. Er ist jetzt allein am Straßenrand und wartet ... Er würde lieber am Telefon auf Statusänderungen warten, aber der Mechaniker würde es nicht tun ... Der Mechaniker sagte, er müsse mit seiner Arbeit weitermachen und könne nicht einfach am Telefon rumhängen. Der Mechaniker versprach, ihn so schnell wie möglich zurückzurufen. Nach ca. 4 Stunden gibt der Typ auf und ruft Uber an. - Beispiel für eine Zeitüberschreitung.
Barrypicker
@ Barrypicker :-D Genial!
Johannes Fahrenkrug
159

Angular1

Für Menschen, die AngularJS verwenden , kann diese Situation mit umgehen Promises.

Hier heißt es:

Versprechen können verwendet werden, um asynchrone Funktionen zu entschlüsseln, und ermöglichen es, mehrere Funktionen miteinander zu verketten.

Sie können eine schöne Erklärung finden hier auch.

Beispiel in den unten genannten Dokumenten .

  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 und höher

In Angular2mit Blick auf das folgende Beispiel, aber seine empfohlen zu Verwendung Observablesmit Angular2.

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

}}

Sie können das auf diese Weise konsumieren,

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

Siehe den Originalbeitrag hier. Typescript unterstützt jedoch keine nativen es6-Versprechen . Wenn Sie es verwenden möchten, benötigen Sie möglicherweise ein Plugin dafür.

Außerdem ist hier die Versprechen SPEC hier definieren.

Maleen Abewardana
quelle
15
Dies erklärt jedoch nicht, wie Versprechen dieses Problem überhaupt lösen würden.
Benjamin Gruenbaum
4
Die Methoden jQuery und fetch geben ebenfalls Versprechen zurück. Ich würde vorschlagen, Ihre Antwort zu überarbeiten. Obwohl jQuery's nicht ganz dasselbe ist (dann ist es da, aber catch nicht).
Tracker1
153

Die meisten Antworten hier geben nützliche Vorschläge, wenn Sie eine einzelne asynchrone Operation ausführen. Manchmal tritt dies jedoch auf, wenn Sie für jeden Eintrag in einem Array oder einer anderen listenartigen Struktur eine asynchrone Operation ausführen müssen . Die Versuchung ist, dies zu tun:

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

Beispiel:

Der Grund, der nicht funktioniert, ist, dass die Rückrufe von doSomethingAsyncnoch nicht ausgeführt wurden, als Sie versuchen, die Ergebnisse zu verwenden.

Wenn Sie also ein Array (oder eine Liste) haben und für jeden Eintrag asynchrone Operationen ausführen möchten, haben Sie zwei Möglichkeiten: Führen Sie die Operationen parallel (überlappend) oder in Reihe (nacheinander nacheinander) aus.

Parallel

Sie können alle starten und verfolgen, wie viele Rückrufe Sie erwarten, und dann die Ergebnisse verwenden, wenn Sie so viele Rückrufe erhalten haben:

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

Beispiel:

(Wir könnten abschaffen expectingund nur verwenden results.length === theArray.length, aber das lässt uns offen für die Möglichkeit, theArraydie geändert wird, während die Anrufe ausstehen ...)

Beachten Sie, wie wir das indexfrom verwenden forEach, um das Ergebnis an resultsderselben Position wie den Eintrag zu speichern, auf den es sich bezieht, auch wenn die Ergebnisse nicht in der richtigen Reihenfolge eintreffen (da asynchrone Aufrufe nicht unbedingt in der Reihenfolge abgeschlossen werden müssen, in der sie gestartet wurden).

Aber was , wenn Sie brauchen , um zurückzukehren diese Ergebnisse aus einer Funktion? Wie die anderen Antworten gezeigt haben, können Sie nicht; Ihre Funktion muss einen Rückruf annehmen und zurückrufen (oder ein Versprechen zurückgeben ). Hier ist eine Rückrufversion:

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

Beispiel:

Oder hier ist eine Version, die Promisestattdessen a zurückgibt:

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

Wenn doSomethingAsyncuns Fehler übergeben werden, rejectlehnen wir das Versprechen natürlich ab, wenn wir einen Fehler erhalten.)

Beispiel:

(Alternativ können Sie auch einen Wrapper erstellen doSomethingAsync, der ein Versprechen zurückgibt, und dann die folgenden Schritte ausführen ...)

Wenn doSomethingAsyncSie ein Versprechen erhalten , können Sie Folgendes verwenden Promise.all:

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

Wenn Sie wissen, doSomethingAsyncdass ein zweites und ein drittes Argument ignoriert werden, können Sie es direkt an übergeben map( mapruft den Rückruf mit drei Argumenten auf, aber die meisten Leute verwenden meistens nur das erste):

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

Beispiel:

Beachten Sie, dass Promise.alldas Versprechen mit einer Reihe von Ergebnissen aller Versprechen aufgelöst wird, die Sie ihm geben, wenn sie alle gelöst sind, oder das Versprechen ablehnt, wenn das erste der Versprechen, die Sie ihm geben, abgelehnt wird.

Serie

Angenommen, Sie möchten nicht, dass die Operationen parallel ablaufen? Wenn Sie sie nacheinander ausführen möchten, müssen Sie warten, bis jeder Vorgang abgeschlossen ist, bevor Sie mit dem nächsten beginnen. Hier ist ein Beispiel für eine Funktion, die dies tut und einen Rückruf mit dem Ergebnis aufruft:

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

(Da wir die Arbeit in Serie results.push(result)ausführen , können wir sie nur verwenden, da wir wissen, dass wir keine fehlerhaften Ergebnisse erzielen. Oben hätten wir sie verwenden können results[index] = result;, aber in einigen der folgenden Beispiele haben wir keinen Index benutzen.)

Beispiel:

(Oder bauen Sie erneut einen Wrapper doSomethingAsync, der Ihnen ein Versprechen gibt, und führen Sie die folgenden Schritte aus ...)

Wenn doSomethingAsyncSie ein Versprechen erhalten, wenn Sie die ES2017 + -Syntax verwenden können (möglicherweise mit einem Transpiler wie Babel ), können Sie eine asyncFunktion mit for-ofund verwenden 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);
});

Beispiel:

Wenn Sie die ES2017 + -Syntax (noch) nicht verwenden können, können Sie eine Variation des Musters "Versprechen reduzieren" verwenden (dies ist komplexer als das übliche Versprechen reduzieren, da wir das Ergebnis nicht von einem zum nächsten weitergeben, sondern stattdessen Sammeln ihrer Ergebnisse in einem 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);
});

Beispiel:

... was mit ES2015 + Pfeilfunktionen weniger umständlich ist :

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

Beispiel:

TJ Crowder
quelle
1
Können Sie if (--expecting === 0)bitte erklären, wie der Teil des Codes funktioniert? Die Callback-Version Ihrer Lösung funktioniert hervorragend für mich. Ich verstehe nur nicht, wie Sie mit dieser Aussage die Anzahl der abgeschlossenen Antworten überprüfen. Schätzen Sie, dass es nur ein Mangel an Wissen meinerseits ist. Gibt es eine alternative Möglichkeit, einen Scheck zu schreiben?
Sarah
@ Sarah: expectingBeginnt mit dem Wert von. Dies ist die Anzahl der array.lengthAnfragen, die wir stellen werden. Wir wissen, dass der Rückruf erst aufgerufen wird, wenn alle diese Anforderungen gestartet wurden. Im Rückruf wird if (--expecting === 0)Folgendes ausgeführt: 1. Dekremente expecting(wir haben eine Antwort erhalten, daher erwarten wir eine Antwort weniger) und wenn der Wert nach dem Dekrement 0 ist (wir erwarten keine weiteren Antworten), sind wir erledigt!
TJ Crowder
1
@PatrickRoberts - Danke !! Ja, Fehler beim Kopieren und Einfügen, dieses zweite Argument wurde in diesem Beispiel vollständig ignoriert (was der einzige Grund ist, warum es nicht fehlgeschlagen ist, da es, wie Sie betont haben, resultsnicht vorhanden war). :-) Repariert.
TJ Crowder
111

Schauen Sie sich dieses Beispiel an:

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

Wie Sie sehen können, getJokewird ein gelöstes Versprechen zurückgegeben (es wird bei der Rückkehr gelöst res.data.value). Sie warten also, bis die Anforderung $ http.get abgeschlossen ist, und führen dann console.log (res.joke) aus (als normaler asynchroner Ablauf).

Dies ist die plnkr:

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

ES6 Weg (asynchron - warten)

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

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
Francisco Carmona
quelle
107

Dies ist einer der Orte, an denen Datenbindungs- oder Speicherkonzepte auf zwei Arten , die in vielen neuen JavaScript-Frameworks verwendet werden, für Sie hervorragend geeignet sind ...

Wenn Sie also Angular, React oder ein anderes Framework verwenden, das Datenbindungs- oder Speicherkonzepte auf zwei Arten ausführt , wird dieses Problem einfach für Sie behoben. Kurz gesagt, Ihr Ergebnis befindet sich undefinedin der ersten Phase, sodass Sie es erhalten, result = undefinedbevor Sie das erhalten Daten, sobald Sie das Ergebnis erhalten, werden sie aktualisiert und dem neuen Wert zugewiesen, der auf Ihren Ajax-Aufruf reagiert ...

Aber wie können Sie es zum Beispiel in reinem Javascript oder jQuery machen, wie Sie in dieser Frage gefragt haben?

Sie können einen Rückruf , ein Versprechen und eine kürzlich beobachtbare Funktion verwenden , um dies für Sie zu erledigen. Beispielsweise haben wir in Versprechungen eine Funktion wie success()oder, then()die ausgeführt wird, wenn Ihre Daten für Sie bereit sind, ebenso wie die Rückruf- oder Abonnementfunktion für beobachtbar .

In Ihrem Fall, in dem Sie jQuery verwenden , können Sie beispielsweise Folgendes tun:

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

Weitere Informationen finden Sie in den Versprechungen und Observablen, die neuere Methoden für diese asynchronen Aufgaben darstellen.

Alireza
quelle
Dies ist im globalen Bereich in Ordnung, aber in einigen Modulkontexten möchten Sie wahrscheinlich den richtigen Kontext für den Rückruf sicherstellen, z. B.$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims
8
Dies ist tatsächlich falsch, da React eine Einweg-Datenbindung ist
Matthew Brent
@MatthewBrent Sie sind nicht falsch, aber auch nicht richtig, React Requisiten sind Objekt und wenn sie geändert werden, ändern sie sich in der gesamten Anwendung, aber es ist keine Möglichkeit, die React Entwickler empfehlen, es zu verwenden ...
Alireza
98

Es ist ein sehr häufiges Problem, mit dem wir konfrontiert sind, wenn wir mit den "Geheimnissen" von JavaScript kämpfen. Lassen Sie mich heute versuchen, dieses Rätsel zu entmystifizieren.

Beginnen wir mit einer einfachen JavaScript-Funktion:

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

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

Dies ist ein einfacher synchroner Funktionsaufruf (bei dem jede Codezeile vor der nächsten nacheinander mit ihrem Job "fertig" ist) und das Ergebnis das gleiche wie erwartet ist.

Fügen wir nun ein wenig Twist hinzu, indem wir unsere Funktion geringfügig verzögern, damit nicht alle Codezeilen nacheinander "fertig" sind. Somit wird das asynchrone Funktionsverhalten emuliert:

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

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

Also los, diese Verzögerung hat gerade die Funktionalität gebrochen, die wir erwartet hatten! Aber was genau ist passiert? Nun, es ist eigentlich ziemlich logisch, wenn man sich den Code ansieht. Die Funktion foo()gibt bei der Ausführung nichts zurück (der zurückgegebene Wert ist also undefined), startet jedoch einen Timer, der eine Funktion nach 1s ausführt, um 'wohoo' zurückzugeben. Aber wie Sie sehen können, ist der Wert, der bar zugewiesen wird, das sofort zurückgegebene Material von foo (), was nichts anderes ist als gerecht undefined.

Wie gehen wir dieses Problem an?

Fragen wir unsere Funktion nach einem VERSPRECHEN . Bei Promise geht es wirklich darum, was es bedeutet: Es bedeutet, dass die Funktion Ihnen garantiert, dass Sie jede Ausgabe liefern, die es in Zukunft erhält. Lassen Sie es uns für unser kleines Problem oben in Aktion sehen:

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

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

Die Zusammenfassung lautet also: Um die asynchronen Funktionen wie Ajax-basierte Aufrufe usw. anzugehen, können Sie ein Versprechen für resolveden Wert verwenden (den Sie zurückgeben möchten). Kurz gesagt, Sie lösen den Wert in asynchronen Funktionen auf , anstatt ihn zurückzugeben .

UPDATE (Versprechen mit Async / Warten)

Neben der Verwendung then/catchzur Arbeit mit Versprechungen gibt es noch einen weiteren Ansatz. Die Idee ist, eine asynchrone Funktion zu erkennen und dann auf die Auflösung der Versprechen zu warten , bevor mit der nächsten Codezeile fortgefahren wird. Es ist immer noch nur promisesunter der Haube, aber mit einem anderen syntaktischen Ansatz. Um die Dinge klarer zu machen, finden Sie unten einen Vergleich:

dann / catch Version:

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

asynchrone / warte Version:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }
Anish K.
quelle
Wird dies immer noch als der beste Weg angesehen, um einen Wert aus einem Versprechen oder einem asynchronen / erwarteten Ergebnis zurückzugeben?
Edwardsmarkf
3
@edwardsmarkf Persönlich glaube ich nicht, dass es einen besten Weg als solchen gibt. Ich verwende Versprechen mit then / catch, async / await sowie Generatoren für asynchrone Teile meines Codes. Dies hängt weitgehend vom Nutzungskontext ab.
Anish K.
96

Ein anderer Ansatz, um einen Wert von einer asynchronen Funktion zurückzugeben, besteht darin, ein Objekt zu übergeben, das das Ergebnis der asynchronen Funktion speichert.

Hier ist ein Beispiel dafür:

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

Ich verwende das resultObjekt, um den Wert während der asynchronen Operation zu speichern. Dadurch ist das Ergebnis auch nach dem asynchronen Job verfügbar.

Ich benutze diesen Ansatz oft. Mich würde interessieren, wie gut dieser Ansatz funktioniert, wenn das Ergebnis über aufeinanderfolgende Module zurückverdrahtet wird.

jsbisht
quelle
9
Es ist nichts Besonderes, hier ein Objekt zu verwenden. Es würde auch funktionieren, wenn Sie ihm die Antwort direkt zuweisen würden result. Dies funktioniert, weil Sie die Variable nach Abschluss der asynchronen Funktion lesen .
Felix Kling
85

Während Versprechen und Rückrufe in vielen Situationen gut funktionieren, ist es ein Problem, etwas auszudrücken wie:

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

Sie würden am Ende durchgehen async1; Überprüfen Sie, ob nameundefiniert ist oder nicht, und rufen Sie den Rückruf entsprechend auf.

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

async1(name, async2)

Während es in kleinen Beispielen in Ordnung ist, wird es ärgerlich, wenn Sie viele ähnliche Fälle und Fehlerbehandlungen haben.

Fibers hilft bei der Lösung des Problems.

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
}

Sie können das Projekt hier auschecken .

rohithpr
quelle
1
@recurf - Es ist nicht mein Projekt. Sie könnten versuchen, ihren Issue-Tracker zu verwenden.
Rohithpr
1
ähnelt dies den Generatorfunktionen? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… *
Emanegux
1
Ist das noch relevant?
Aluan Haddad
Sie können verwenden, async-awaitwenn Sie einige der neuesten Versionen von Node verwenden. Wenn jemand mit älteren Versionen nicht weiterkommt, kann er diese Methode verwenden.
Rohithpr
83

Das folgende Beispiel, das ich geschrieben habe, zeigt, wie es geht

  • Asynchrone HTTP-Aufrufe verarbeiten;
  • Warten Sie auf die Antwort von jedem API-Aufruf.
  • Verwenden Sie das Versprechungsmuster .
  • Verwenden Sie das Promise.all- Muster, um mehrere HTTP-Aufrufe zu verbinden.

Dieses Arbeitsbeispiel ist in sich geschlossen. Es wird ein einfaches Anforderungsobjekt definiert, das das Fensterobjekt XMLHttpRequestzum Tätigen von Anrufen verwendet. Es wird eine einfache Funktion definiert, mit der darauf gewartet werden kann, dass eine Reihe von Versprechungen erfüllt werden.

Kontext. In diesem Beispiel wird der Spotify-Web-API- Endpunkt playlistabgefragt , um nach Objekten für einen bestimmten Satz von Abfragezeichenfolgen zu suchen :

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

Für jedes Element löst ein neues Versprechen einen Block aus ExecutionBlock. Analysieren Sie das Ergebnis, planen Sie einen neuen Satz von Versprechen basierend auf dem Ergebnisarray, dh einer Liste von Spotify- userObjekten, und führen Sie den neuen HTTP-Aufruf innerhalb des ExecutionProfileBlockasynchronen Versuchs aus.

Sie können dann eine verschachtelte Promise-Struktur sehen, mit der Sie mehrere und vollständig asynchrone verschachtelte HTTP-Aufrufe erzeugen und die Ergebnisse aus jeder Teilmenge von Aufrufen zusammenführen können Promise.all.

HINWEIS Für neuere Spotify- searchAPIs muss ein Zugriffstoken in den Anforderungsheadern angegeben werden:

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

Um das folgende Beispiel auszuführen, müssen Sie Ihr Zugriffstoken in die Anforderungsheader einfügen:

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

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

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

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

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

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

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

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

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

    ///// promiseall again

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

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

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

    /////

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

Ich habe diese Lösung hier ausführlich besprochen .

loretoparisi
quelle
80

Die kurze Antwort lautet: Sie müssen einen Rückruf wie folgt implementieren:

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

$.ajax({
    url: "...",
    success: callback
});
Pablo Matias Gomez
quelle
78

Antwort 2017: Sie können jetzt in jedem aktuellen Browser und Knoten genau das tun, was Sie wollen

Das ist ganz einfach:

  • Geben Sie ein Versprechen zurück
  • Verwenden Sie das 'Warten' , das JavaScript anweist, auf das Versprechen zu warten, das in einen Wert aufgelöst werden soll (wie die HTTP-Antwort).
  • Fügen Sie der übergeordneten Funktion das Schlüsselwort 'async' hinzu

Hier ist eine funktionierende Version Ihres Codes:

(async function(){

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

})()

Warten wird in allen aktuellen Browsern und Knoten 8 unterstützt

Mikemaccana
quelle
7
Leider funktioniert dies nur mit Funktionen, die Versprechen zurückgeben - zum Beispiel nicht mit der Node.js-API, die Rückrufe verwendet. Und ich würde es nicht empfehlen, es ohne Babel zu verwenden, da nicht jeder "aktuelle Browser" verwendet.
Michał Perłakowski
2
@ MichałPerłakowski Knoten 8 enthält nodejs.org/api/util.html#util_util_promisify_original, mit dem die API von node.js Versprechen zurückgeben kann. Ob Sie Zeit und Geld haben, um nicht aktuelle Browser zu unterstützen, hängt natürlich von Ihrer Situation ab.
Mikemaccana
IE 11 ist leider noch ein aktueller Browser im Jahr 2018 und wird nicht unterstütztawait/async
Juan Mendes
IE11 ist kein aktueller Browser. Es wurde vor 5 Jahren veröffentlicht und hat laut caniuse einen weltweiten Marktanteil von 2,5%. Wenn nicht jemand Ihr Budget verdoppelt, um alle aktuellen Technologien zu ignorieren, ist es die Zeit der meisten Menschen nicht wert.
Mikemaccana
76

Js ist ein Single-Threaded.

Der Browser kann in drei Teile unterteilt werden:

1) Ereignisschleife

2) Web-API

3) Ereigniswarteschlange

Die Ereignisschleife wird für immer ausgeführt, dh für eine Art Endlosschleife. In der Ereigniswarteschlange werden alle Ihre Funktionen auf ein Ereignis verschoben (Beispiel: Klicken). Dies wird einzeln aus der Warteschlange ausgeführt und in die Ereignisschleife gestellt, die diese Funktion ausführt und selbst vorbereitet Dies bedeutet, dass die Ausführung einer Funktion erst beginnt, wenn die Funktion, bevor sie sich in der Warteschlange befindet, in der Ereignisschleife ausgeführt wird.

Nehmen wir nun an, wir haben zwei Funktionen in eine Warteschlange verschoben. Eine dient zum Abrufen von Daten vom Server und eine andere verwendet diese Daten. Wir haben zuerst die Funktion serverRequest () in die Warteschlange gestellt und dann die Funktion useData () verwendet. Die Funktion serverRequest geht in die Ereignisschleife und ruft den Server auf, da wir nie wissen, wie viel Zeit erforderlich ist, um Daten vom Server abzurufen. Daher wird erwartet, dass dieser Prozess einige Zeit in Anspruch nimmt, und wir beschäftigen unsere Ereignisschleife, sodass unsere Seite hängt. Dort hängt das Web Die API kommt in die Rolle, indem sie diese Funktion aus der Ereignisschleife übernimmt und sich mit dem Server befasst, der die Ereignisschleife frei macht, damit wir die nächste Funktion aus der Warteschlange ausführen können. Die nächste Funktion in der Warteschlange ist utiliseData (), die in die Schleife geht, aber da keine Daten verfügbar sind Verschwendung und Ausführung der nächsten Funktion werden bis zum Ende der Warteschlange fortgesetzt. (Dies wird als Async-Aufruf bezeichnet, dh wir können etwas anderes tun, bis wir Daten erhalten.)

Angenommen, unsere Funktion serverRequest () hatte eine return-Anweisung in einem Code. Wenn wir Daten von der Server-Web-API zurückerhalten, werden sie am Ende der Warteschlange in die Warteschlange gestellt. Da es am Ende in die Warteschlange verschoben wird, können wir seine Daten nicht verwenden, da in unserer Warteschlange keine Funktion mehr zum Verwenden dieser Daten vorhanden ist. Daher ist es nicht möglich, etwas von Async Call zurückzugeben.

Die Lösung hierfür ist also ein Rückruf oder Versprechen .

Ein Bild aus einer der Antworten hier, erklärt die Verwendung von Rückrufen korrekt ... Wir geben unsere Funktion (Funktion unter Verwendung der vom Server zurückgegebenen Daten) an den aufrufenden Server weiter.

Ruf zurück

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

}

In meinem Code heißt es als

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

Rückruf von Javscript.info

Aniket Jha
quelle
68

Sie können diese benutzerdefinierte Bibliothek (geschrieben mit Promise) verwenden, um einen Remote-Anruf zu tätigen.

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

Einfaches Verwendungsbeispiel:

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

Eine andere Lösung besteht darin, Code über den sequentiellen Executor nsynjs auszuführen .

Wenn die zugrunde liegende Funktion versprochen ist

nsynjs wertet alle Versprechen nacheinander aus und legt das Versprechen-Ergebnis in die dataEigenschaft:

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>

Wenn die zugrunde liegende Funktion nicht versprochen wird

Schritt 1. Wrap-Funktion mit Rückruf in nsynjs-fähigen Wrapper (wenn die Version versprochen ist, können Sie diesen Schritt überspringen):

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;

Schritt 2. Synchrone Logik in Funktion setzen:

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

Schritt 3. Führen Sie die Funktion synchron über nsynjs aus:

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

Nsynjs wertet alle Operatoren und Ausdrücke Schritt für Schritt aus und unterbricht die Ausführung, falls das Ergebnis einer langsamen Funktion nicht bereit ist.

Weitere Beispiele hier: https://github.com/amaksr/nsynjs/tree/master/examples

amaksr
quelle
2
Das ist interessant. Mir gefällt, wie es ermöglicht, asynchrone Aufrufe so zu codieren, wie Sie es in anderen Sprachen tun würden. Aber technisch gesehen ist es kein echtes JavaScript?
J Morris
41

ECMAScript 6 verfügt über 'Generatoren', mit denen Sie problemlos asynchron programmieren können.

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

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

Um den obigen Code auszuführen, gehen Sie folgendermaßen vor:

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

Wenn Sie auf Browser abzielen müssen, die ES6 nicht unterstützen, können Sie den Code über Babel oder den Closure-Compiler ausführen, um ECMAScript 5 zu generieren.

Die Rückrufe ...argswerden in ein Array eingeschlossen und beim Lesen zerstört, sodass das Muster Rückrufe mit mehreren Argumenten verarbeiten kann. Zum Beispiel mit dem Knoten fs :

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

Hier sind einige Ansätze zum Arbeiten mit asynchronen Anforderungen:

  1. Browser-Versprechungsobjekt
  2. F - Eine Versprechensbibliothek für JavaScript
  3. A + Promises.js
  4. jQuery zurückgestellt
  5. XMLHttpRequest API
  6. Rückrufkonzept verwenden - Als Implementierung in der ersten Antwort

Beispiel: Die Implementierung von jQuery wurde verschoben, um mit mehreren Anforderungen zu arbeiten

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

Mohan Dere
quelle
38

Wir befinden uns in einem Universum, das sich in einer Dimension zu entwickeln scheint, die wir "Zeit" nennen. Wir verstehen nicht wirklich, was Zeit ist, aber wir haben Abstraktionen und Vokabeln entwickelt, mit denen wir argumentieren und darüber sprechen können: "Vergangenheit", "Gegenwart", "Zukunft", "vor", "nach".

Die Computersysteme, die wir bauen, haben immer mehr Zeit als wichtige Dimension. Bestimmte Dinge sind für die Zukunft vorgesehen. Dann müssen andere Dinge passieren, nachdem diese ersten Dinge schließlich geschehen sind. Dies ist der Grundbegriff, der als "Asynchronität" bezeichnet wird. In unserer zunehmend vernetzten Welt wartet der häufigste Fall von Asynchronität darauf, dass ein Remote-System auf eine Anfrage reagiert.

Betrachten Sie ein Beispiel. Sie rufen den Milchmann an und bestellen etwas Milch. Wenn es darum geht, möchten Sie es in Ihren Kaffee geben. Sie können die Milch momentan nicht in Ihren Kaffee geben, da sie noch nicht hier ist. Sie müssen warten, bis es kommt, bevor Sie es in Ihren Kaffee geben. Mit anderen Worten, Folgendes funktioniert nicht:

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

Da JS hat keine Möglichkeit , zu wissen , dass es muss warten für order_milkzu beenden , bevor sie ausgeführt wird put_in_coffee. Mit anderen Worten, weiß er nicht , dass order_milkist asynchron --is etwas , das erst später einmal in Milch würde zur Folge hat . JS und andere deklarative Sprachen führen eine Anweisung nach der anderen aus, ohne zu warten.

Der klassische JS-Ansatz für dieses Problem, bei dem die Tatsache ausgenutzt wird, dass JS Funktionen als erstklassige Objekte unterstützt, die weitergegeben werden können, besteht darin, eine Funktion als Parameter an die asynchrone Anforderung zu übergeben, die sie nach Abschluss aufruft seine Aufgabe irgendwann in der Zukunft. Das ist der "Rückruf" -Ansatz. Es sieht aus wie das:

order_milk(put_in_coffee);

order_milk startet, bestellt die Milch und ruft dann, wenn und nur wenn sie eintrifft, an put_in_coffee .

Das Problem bei diesem Rückrufansatz besteht darin, dass er die normale Semantik einer Funktion verschmutzt, mit der das Ergebnis gemeldet wird return. Stattdessen dürfen Funktionen ihre Ergebnisse nicht durch Aufrufen eines als Parameter angegebenen Rückrufs melden. Dieser Ansatz kann auch bei längeren Ereignissequenzen schnell unhandlich werden. Nehmen wir zum Beispiel an, ich möchte warten, bis die Milch in den Kaffee gegeben wird, und dann und erst dann einen dritten Schritt ausführen, nämlich den Kaffee zu trinken. Am Ende muss ich so etwas schreiben:

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

wo ich put_in_coffeesowohl die Milch zum Einfüllen als auch die Aktion übergebe (drink_coffee ), die ausgeführt werden soll, sobald die Milch eingegeben wurde. Ein solcher Code ist schwer zu schreiben, zu lesen und zu debuggen.

In diesem Fall könnten wir den Code in der Frage wie folgt umschreiben:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Versprechen eingeben

Dies war die Motivation für die Vorstellung eines "Versprechens", einer bestimmten Art von Wert, der eine Zukunft darstellt oder asynchrones Ergebnis darstellt. Es kann etwas darstellen, das bereits passiert ist oder das in Zukunft passieren wird oder möglicherweise überhaupt nicht passieren wird. Versprechen haben eine einzige Methode mit dem Namen then, an die Sie eine Aktion übergeben, die ausgeführt werden soll, wenn das Ergebnis, das das Versprechen darstellt, realisiert wurde.

Bei unserer Milch und unserem Kaffee möchten wir order_milkein Versprechen für die ankommende Milch zurückgeben und dann put_in_coffeeals angebenthen Aktion wie folgt :

order_milk() . then(put_in_coffee)

Ein Vorteil davon ist, dass wir diese aneinanderreihen können, um Sequenzen zukünftiger Ereignisse zu erstellen ("Verkettung"):

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

Lassen Sie uns Versprechen auf Ihr spezielles Problem anwenden. Wir werden unsere Anforderungslogik in eine Funktion einschließen, die ein Versprechen zurückgibt:

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

Eigentlich haben wir returndem Anruf an nur ein hinzugefügt $.ajax. Dies funktioniert, weil jQuery $.ajaxbereits eine Art Versprechen zurückgibt. (In der Praxis würden wir diesen Aufruf, ohne auf Details einzugehen, vorziehen, um ein echtes Versprechen zurückzugeben, oder eine Alternative dazu $.ajaxverwenden.) Nun, wenn wir die Datei laden und warten möchten, bis sie fertig ist und dann mach etwas, können wir einfach sagen

get_data() . then(do_something)

zum Beispiel,

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

Wenn wir Versprechen verwenden, übergeben wir am Ende viele Funktionen then ist es oft hilfreich, die kompakteren Pfeilfunktionen im ES6-Stil zu verwenden:

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

Das asyncSchlüsselwort

Aber es ist immer noch etwas vage Unbefriedigendes, Code auf eine Weise schreiben zu müssen, wenn er synchron ist, und auf eine ganz andere Art, wenn er asynchron ist. Für synchron schreiben wir

a();
b();

aber wenn aes asynchron ist, müssen wir mit Versprechungen schreiben

a() . then(b);

Oben haben wir gesagt: "JS kann nicht wissen, dass es warten muss, bis der erste Aufruf beendet ist, bevor der zweite ausgeführt wird." Wäre es nicht schön, wenn es ist eine Möglichkeit , JS zu sagen , dass? Es stellt sich heraus, dass es - das awaitSchlüsselwort gibt, das in einem speziellen Funktionstyp verwendet wird, der als "asynchrone" Funktion bezeichnet wird. Diese Funktion ist Teil der kommenden Version von ES, ist jedoch bereits in Transpilern wie Babel mit den richtigen Voreinstellungen verfügbar. Dies ermöglicht es uns, einfach zu schreiben

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

In Ihrem Fall könnten Sie so etwas schreiben

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

Kurze Antwort : Ihre foo()Methode wird sofort zurückgegeben, während der $ajax()Aufruf nach der Rückkehr der Funktion asynchron ausgeführt wird . Das Problem ist dann, wie oder wo die vom asynchronen Aufruf nach seiner Rückkehr abgerufenen Ergebnisse gespeichert werden sollen.

In diesem Thread wurden mehrere Lösungen angegeben. Am einfachsten ist es vielleicht, ein Objekt an die foo()Methode zu übergeben und die Ergebnisse nach Abschluss des asynchronen Aufrufs in einem Mitglied dieses Objekts zu speichern.

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

Beachten Sie, dass der Aufruf von foo()immer noch nichts Nützliches zurückgibt. Das Ergebnis des asynchronen Aufrufs wird nun jedoch in gespeichert result.response.

David R Tribble
quelle
14
Während dies funktioniert, ist es nicht wirklich besser, als einer globalen Variablen zuzuweisen.
Felix Kling
36

Verwenden Sie eine callback()Funktion innerhalb des foo()Erfolgs. Versuchen Sie es auf diese Weise. Es ist einfach und leicht zu verstehen.  

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
Mahfuzur Rahman
quelle
29

Die Frage war:

Wie gebe ich die Antwort von einem asynchronen Aufruf zurück?

was interpretiert werden kann als:

Wie kann asynchroner Code synchron aussehen ?

Die Lösung besteht darin, Rückrufe zu vermeiden und eine Kombination aus Versprechen und asynchronem / Warten zu verwenden .

Ich möchte ein Beispiel für eine Ajax-Anfrage geben.

(Obwohl es in Javascript geschrieben werden kann, ziehe ich es vor, es in Python zu schreiben und es mit Transcrypt in Javascript zu kompilieren . Es wird klar genug sein.)

Aktivieren Sie zunächst die Verwendung von JQuery, um Folgendes $verfügbar zu machen S:

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

Definieren Sie eine Funktion, die ein Versprechen zurückgibt , in diesem Fall einen Ajax-Aufruf:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Verwenden Sie den asynchronen Code so, als wäre er synchron :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
Pieter Jan Bonestroo
quelle
29

Versprechen verwenden

Die perfekteste Antwort auf diese Frage ist die Verwendung von Promise.

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

Verwendungszweck

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

Aber warte...!

Es gibt ein Problem mit der Verwendung von Versprechungen!

Warum sollten wir unser eigenes Versprechen verwenden?

Ich habe diese Lösung eine Weile verwendet, bis ich herausgefunden habe, dass in alten Browsern ein Fehler vorliegt:

Uncaught ReferenceError: Promise is not defined

Also habe ich beschlossen, meine eigene Promise-Klasse für ES3 unter js Compilern zu implementieren, wenn sie nicht definiert ist. Fügen Sie diesen Code einfach vor Ihrem Hauptcode hinzu und verwenden Sie dann Promise!

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

Natürlich gibt es viele Ansätze wie synchrone Anfragen, Versprechen, aber meiner Erfahrung nach sollten Sie den Rückrufansatz verwenden. Das asynchrone Verhalten von Javascript ist natürlich. Ihr Code-Snippet kann also etwas anders geschrieben werden:

function foo() {
    var result;

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

    return result;
}

function myCallback(response) {
    // Does something.
}
Khoa Bui
quelle
5
Rückrufe oder JavaScript sind von Natur aus nicht asynchron.
Aluan Haddad
19

Anstatt Code auf Sie zu werfen, gibt es zwei Konzepte, die entscheidend dafür sind, wie JS mit Rückrufen und Asynchronität umgeht. (Ist das überhaupt ein Wort?)

Das Ereignisschleifen- und Parallelitätsmodell

Es gibt drei Dinge, die Sie beachten müssen; Die Warteschlange; die Ereignisschleife und der Stapel

Im weitesten Sinne ist die Ereignisschleife wie der Projektmanager. Sie wartet ständig auf Funktionen, die ausgeführt werden sollen, und kommuniziert zwischen der Warteschlange und dem Stapel.

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

Sobald es eine Nachricht zum Ausführen von etwas erhält, fügt es sie der Warteschlange hinzu. Die Warteschlange ist die Liste der Dinge, die auf ihre Ausführung warten (wie Ihre AJAX-Anfrage). stell es dir so vor:

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

Wenn eine dieser Nachrichten ausgeführt wird, wird die Nachricht aus der Warteschlange entfernt und ein Stapel erstellt. Der Stapel ist alles, was JS ausführen muss, um die Anweisung in der Nachricht auszuführen. In unserem Beispiel wird also gesagt, dass wir anrufen sollenfoobarFunc

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

Alles, was foobarFunc ausführen muss (in unserem Fall anotherFunction), wird auf den Stapel verschoben . ausgeführt und dann vergessen - die Ereignisschleife wechselt dann zum nächsten Element in der Warteschlange (oder wartet auf Nachrichten).

Der Schlüssel hier ist die Reihenfolge der Ausführung. Das ist

WANN wird etwas laufen

Wenn Sie mit AJAX einen Anruf bei einer externen Partei tätigen oder einen asynchronen Code ausführen (z. B. ein setTimeout), ist Javascript von einer Antwort abhängig, bevor es fortgesetzt werden kann.

Die große Frage ist, wann die Antwort kommt. Die Antwort ist, dass wir es nicht wissen - also wartet die Ereignisschleife darauf, dass diese Nachricht "hey run me" lautet. Wenn JS nur synchron auf diese Nachricht warten würde, würde Ihre App einfrieren und sie würde saugen. Daher führt JS das nächste Element in der Warteschlange weiter aus, während er darauf wartet, dass die Nachricht wieder zur Warteschlange hinzugefügt wird.

Aus diesem Grund verwenden wir bei asynchronen Funktionen sogenannte Rückrufe . Es ist buchstäblich wie ein Versprechen . Wie in Ich verspreche, irgendwann etwas zurückzugeben, verwendet jQuery bestimmte Rückrufe namens deffered.done deffered.failund deffered.always(unter anderem). Sie können sie alle hier sehen

Sie müssen also eine Funktion übergeben, deren Ausführung versprochen wird, und zwar mit Daten, die an sie übergeben werden.

Da ein Rückruf nicht sofort ausgeführt wird, sondern zu einem späteren Zeitpunkt, ist es wichtig, den Verweis auf die nicht ausgeführte Funktion zu übergeben. damit

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

Die meiste Zeit (aber nicht immer) wirst du foonicht bestehenfoo()

Hoffentlich macht das Sinn. Wenn Sie auf solche Dinge stoßen, die verwirrend erscheinen, empfehle ich dringend, die Dokumentation vollständig zu lesen, um zumindest ein Verständnis dafür zu bekommen. Es wird Sie zu einem viel besseren Entwickler machen.

Matthew Brent
quelle
18

Mit ES2017 sollten Sie dies als Funktionsdeklaration haben

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

Und es so ausführen.

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

Oder die Promise-Syntax

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

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

})
Fernando Carvajal
quelle
Könnte diese zweite Funktion wiederverwendbar sein?
Zum Dummi
Wie verwenden Sie die Ergebnisse, wenn oncolse, log aufgerufen wird? Geht an diesem Punkt nicht alles zur Konsole?
Ken Ingram