it-swarm.com.de

Wie kann ich die Antwort von einem asynchronen Anruf zurückgeben?

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

Ich habe versucht, den Wert aus dem success -Rückruf zurückzugeben und 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`.
5085
Felix Kling

→ Eine allgemeinere Erläuterung 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 Codereferenz

→ 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 heißt, das Senden der Anfrage (bzw. das Empfangen der Antwort) wird aus dem normalen Ausführungsfluss herausgenommen. In Ihrem Beispiel gibt $.ajax sofort zurück und die nächste Anweisung, return result;, wird ausgeführt, bevor die als success-Rückruf übergebene Funktion überhaupt aufgerufen wurde.

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

Synchron

Stellen Sie sich vor, Sie rufen einen Freund an 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ötigt haben.

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 die Ausführung von findItem viel Zeit in Anspruch nimmt, muss jeder Code nach var item = findItem(); warten , bis die Funktion das Ergebnis zurückgibt.

Asynchron

Sie rufen Ihren Freund aus dem gleichen Grund erneut an. Aber diesmal sagst du ihm, dass du es eilig hast und er soll dich auf deinem Handy zurückrufen . 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, stellen Sie eine Funktion bereit, die aufgerufen wird, sobald die Antwort empfangen wurde, einen Rückruf (etwas beachten? Rückruf ?). Jede nach diesem Aufruf eingehende Anweisung wird ausgeführt, bevor der Rückruf aufgerufen wird.


Lösung (en)

Machen Sie sich die asynchrone Natur von JavaScript zunutze! Bestimmte asynchrone Vorgänge stellen zwar synchrone Gegenstücke bereit (wie auch "Ajax"), es wird jedoch generell davon abgeraten, sie zu verwenden, insbesondere in a Browserkontext.

Warum ist es schlimm, fragst du?

JavaScript wird im Benutzeroberflächenthread des Browsers ausgeführt, und ein längerer Prozess sperrt die Benutzeroberfläche, sodass sie nicht mehr reagiert. Zusätzlich 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 wirklich eine schlechte Benutzererfahrung. Der Benutzer kann nicht feststellen, ob alles einwandfrei funktioniert oder nicht. Darüber hinaus wird der Effekt für Benutzer mit einer langsamen Verbindung schlechter sein.

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

  • Versprechen mit async/await (ES2017 +, verfügbar in älteren Browsern, wenn Sie einen Transpiler oder Regenerator verwenden)
  • Rückrufe (beliebt in Node)
  • Versprechen mit then() (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 +: Verspricht mit async/await

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

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

Wichtig: Sie können await nur innerhalb einer async-Funktion verwenden. Derzeit wird await auf oberster Ebene noch nicht unterstützt. Daher müssen Sie möglicherweise ein asynchrones IIFE ( Ausdruck für sofort aufgerufene Funktionen ) erstellen, um einen async-Kontext zu starten.

Sie können mehr über async und await auf 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);
})();

Die aktuellen Versionen Browser und Knoten unterstützen async/await. Sie können auch ältere Umgebungen unterstützen, indem Sie Ihren Code mit Hilfe von Regenerator (oder Tools, die Regenerator verwenden, wie 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, wenn sie bereit ist. Im Kontext eines asynchronen Prozesses wird der Rückruf immer dann aufgerufen, wenn der asynchrone Prozess abgeschlossen ist. In der Regel wird das Ergebnis an den Callback übergeben.

Im Beispiel der Frage können Sie foo veranlassen, einen Rückruf anzunehmen und als success-Rückruf zu 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
    });
}

callback bezieht sich auf die Funktion, die wir an foo übergeben, wenn wir sie aufrufen, und wir geben sie einfach an success weiter. Das heißt Sobald die Ajax-Anforderung erfolgreich ist, ruft $.ajaxcallback auf und leitet die Antwort an den Rückruf weiter (auf den mit result verwiesen werden kann, da wir den Rückruf so 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 den Anschein hat. Immerhin ist JavaScript im Browser stark ereignisgesteuert (DOM-Ereignisse). Das Empfangen der Ajax-Antwort ist nichts anderes als ein Ereignis.
Es können Schwierigkeiten auftreten, wenn Sie mit Code von Drittanbietern arbeiten müssen. Die meisten Probleme können jedoch durch einfaches Durchdenken des Anwendungsflusses gelöst werden.


ES2015 +: Verspricht mit then ()

Die Promise API ist eine neue Funktion von ECMAScript 6 (ES2015), hat aber bereits eine gute Browser-Unterstü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 zu vereinfachen (z. B. bluebird ).

Versprechen sind Container für zukünftige Werte. Wenn das Versprechen den Wert erhält (es ist gelöst ) oder wenn es storniert ( abgelehnt ) wird, benachrichtigt es alle seine " 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 dass sie einfacher zu verfassen 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 Versprechungen wie diese 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, geht über den Rahmen dieser Antwort hinaus. Wenn Sie jedoch neuen Code schreiben, sollten Sie ernsthaft darüber nachdenken. Sie bieten eine hervorragende Abstraktion und Trennung Ihres Codes.

Weitere Informationen zu Versprechen: HTML5 rockt - JavaScript-Versprechen

Randnotiz: jQueries zurückgestellte Objekte

Zurückgestellte Objekte sind die benutzerdefinierten Implementierungen von Versprechungen in jQuery (vor der Standardisierung der Promise-API). Sie verhalten sich fast wie Versprechungen, zeigen jedoch eine etwas andere API.

Jede Ajax-Methode von jQuery gibt bereits ein "zurückgestelltes Objekt" (eigentlich ein Versprechen eines zurückgestellten Objekts) zurück, 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: Versprich 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 obigen Asynchronitätsprobleme falsch. Insbesondere friert $.ajax() den Code nicht ein, während die Seite '/ password' auf Ihrem Server überprüft wird - es sendet eine Anforderung an den Server und gibt während des Wartens sofort ein jQuery Ajax Deferred-Objekt zurück, nicht die Antwort von der Server. Das bedeutet, dass die Anweisung if dieses verzögerte Objekt immer abruft, es als true behandelt und 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 Entsprechungen. Ich befürworte ihre Verwendung nicht, aber der Vollständigkeit halber würden Sie hier einen synchronen Anruf durchführen:

Ohne jQuery

Wenn Sie direkt ein XMLHTTPRequest Objekt verwenden, übergeben Sie false als drittes Argument an .open .

jQuery

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

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

Wenn Sie eine andere jQuery Ajax-Methode wie $.get, $.getJSON usw. verwenden, müssen Sie diese in $.ajax ändern (da Sie nur Konfigurationsparameter an $.ajax übergeben können).

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

5361
Felix Kling

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, als er eine Antwort für Benutzer von jQuery for AJAX geschrieben hat. Ich habe mich entschieden, eine Alternative für Benutzer bereitzustellen, die dies nicht tun.

( Hinweis: Für Benutzer der neuen fetch-API, Angular oder Versprechen, die ich unten angegeben habe )


Womit du konfrontiert bist

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 heißt, das Senden der Anfrage (bzw. das Empfangen der Antwort) wird aus dem normalen Ausführungsfluss herausgenommen. In Ihrem Beispiel gibt .send sofort zurück und die nächste Anweisung _return result;_ wird ausgeführt, bevor die als success übergebene Funktion überhaupt aufgerufen wurde.

Das heißt, wenn Sie zurückkehren, wurde der von Ihnen definierte Listener noch nicht ausgeführt. 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 zurückgegebene Wert von a ist undefined, da der Teil _a=5_ noch nicht ausgeführt wurde. AJAX verhält sich wie folgt: Sie geben den Wert zurück, bevor der Server die Möglichkeit hat, Ihrem Browser diesen Wert mitzuteilen.

Eine mögliche Lösung für dieses Problem besteht darin, erneut zu codieren und Ihrem Programm mitzuteilen, was zu tun ist, wenn die Berechnung abgeschlossen ist.

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

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

Dies nennt man CPS . Grundsätzlich übergeben wir getFive eine Aktion, die ausgeführt werden soll, wenn sie abgeschlossen ist. Wir teilen unserem Code mit, wie zu reagieren ist, wenn ein Ereignis abgeschlossen ist (z. B. 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. Synchronisieren Sie den AJAX -Aufruf (nennen wir ihn SJAX).
  2. Strukturieren Sie Ihren Code neu, damit er ordnungsgemäß mit Rückrufen funktioniert.

1. Synchrones AJAX - Tu es nicht !!

Was synchrones AJAX betrifft, tu es nicht! Felix 'Antwort wirft einige zwingende Argumente 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 asynchrone Anforderungen aus Leistungsgründen synchronen Anforderungen vorgezogen werden.

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

Wenn Sie müssen , können Sie ein Flag ü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. Restrukturierungscode

Lassen Sie Ihre Funktion einen Rückruf annehmen. Im Beispielcode foo kann ein Rückruf angenommen werden. Wir werden unserem Code sagen, wie zu reagieren ist , wenn foo abgeschlossen 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önnen genauso gut einen Verweis auf eine vorhandene Funktion übergeben, so dass diese wie folgt aussieht:

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

Weitere Informationen zu dieser Art von Rückrufentwurf finden Sie in der Antwort von Felix.

Jetzt definieren wir 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 unsere foo-Funktion jetzt veranlasst, eine auszuführende Aktion zu akzeptieren, wenn AJAX erfolgreich abgeschlossen wurde. Wir können dies weiter ausbauen, indem wir prüfen, ob der Antwortstatus nicht 200 ist, und entsprechend vorgehen (einen Fehlerbehandler und dergleichen erstellen). Effektiv unser Problem zu lösen.

Wenn Sie immer noch Schwierigkeiten haben, dies zu verstehen lesen Sie die AJAX Kurzanleitung bei MDN.

1029

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

Wenn Sie jQuery nicht verwenden und eine nette kurze XMLHttpRequest 2 möchten, die auf den modernen Browsern und auch auf den mobilen Browsern funktioniert, empfehle ich, sie auf diese Weise 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 gesetzt (also keine zusätzlichen unnötigen Abschlüsse).
  3. Es verwendet den neuen Onload (so müssen Sie nicht auf Readystate && Status prüfen)
  4. Es gibt einige andere Situationen, an die ich mich nicht erinnere, die das XMLHttpRequest 1 ärgerlich machen.

Es gibt zwei Möglichkeiten, die Antwort auf diesen Ajax-Aufruf abzurufen (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 (das 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.

Check out XMLHttpRequest erweiterte Funktionen

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

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

Die Verwendung des Variablennamens XMLHttpRequest ist ein weiterer großer Fehler, da Sie den Rückruf innerhalb der Onload-/Oreadystatechange-Closures ausführen müssen, ansonsten haben Sie ihn verloren.


Wenn Sie mit post und FormData etwas Komplexeres wollen, 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 & post.

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 keine Synchronisierung implementiert ... es ist eine schlechte Sache.

Trotzdem ... warum machst du es nicht einfach?


Wie im Kommentar erwähnt, wird durch die Verwendung von error && synchronous der Punkt der Antwort vollständig unterbrochen. Welches ist eine nette Abkürzung, um Ajax richtig einzusetzen?

Fehlerbehandler

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 einen Fehlerbehandler, der 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, muss nur eine falsche URL eingegeben werden. In diesem Fall gibt jeder Browser einen Fehler aus.

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

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

Auch wenn Sie 'fdggdgilfdghfldj' als Formulardaten übergeben, wird kein Fehler ausgegeben.

Im ersten Fall befindet sich der Fehler in der displayAjax() unter this.statusText als Method not Allowed.

Im zweiten Fall funktioniert es einfach. Sie müssen auf der Serverseite überprüfen, ob Sie die richtigen Post-Daten übergeben haben.

domänenübergreifend nicht zulässig löst Fehler automatisch aus.

In der Fehlerantwort gibt es keine Fehlercodes.

Es gibt nur den this.type, der auf error gesetzt ist.

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

Also: Es sind keine Fehlerprüfungen erforderlich, wenn Sie die URL ordnungsgemäß kopieren und einfügen können. ;)

PS: Als ersten Test habe ich x ('x', displayAjax) geschrieben ... und es wurde total geantwortet ... ??? Also überprüfte ich den Ordner, in dem sich der HTML-Code befindet, und es gab eine Datei mit dem Namen 'x.xml'. Also, auch wenn Sie die Erweiterung Ihrer Datei vergessen, wird XMLHttpRequest 2 es finden . Ich habe laut gelacht


Liest eine Datei synchron

Tu das nicht.

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

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

Jetzt kannst du tun

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

Es gibt keine andere Möglichkeit, dies nicht asynchron durchzuführen. (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 für jede Anfrage immer andere Funktionen 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 Sonderfunktion.


Die oben genannten Funktionen sind für den grundlegenden Gebrauch.

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 viele Dinge tun:

Ich habe einen Download-Manager erstellt (mit Bereichen auf beiden Seiten mit Resume, Filereader, Dateisystem), verschiedene Konverter für Bild-Resizer 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 brauchen Sie einen Blob, Array-Puffer, Sie können Header setzen, Mimetype ü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.)

378
cocco

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

Dies bedeutet AngularJS, jQuery (mit Aufschub), native XHR-Ersetzung (Abruf), EmberJS, BackboneJS-Speicherung oder jede Knotenbibliothek, die Versprechungen 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, indem er eine Antwort für Benutzer von jQuery mit Rückrufen für AJAX geschrieben hat. Ich habe eine Antwort für native XHR. Diese Antwort ist für die generische Verwendung von Versprechungen im Frontend oder Backend gedacht.


Das Kernproblem

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

Wann immer Sie eine Methode aufrufen, die ein Versprechen zurückgibt, werden die then -Handler immer asynchron ausgeführt - dh nach den Code darunter, der sich nicht in einem .then -Handler befindet.

Wenn Sie also data zurückgeben, wurde der von Ihnen definierte then -Handler noch nicht ausgeführt. 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 data ist undefined, da der Teil data = 5 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 die Operation noch nicht ausgeführt wurde (AJAX, Serveraufruf, E/A, Timer), geben Sie den Wert zurück, bevor die Anforderung die Möglichkeit hat, Ihrem Code diesen Wert mitzuteilen.

Eine mögliche Lösung für dieses Problem besteht darin, erneut aktiv zu codieren und Ihrem Programm mitzuteilen, was zu tun ist, wenn die Berechnung abgeschlossen ist. Versprechen ermöglichen dies aktiv, indem sie zeitlicher Natur sind.

Kurze Zusammenfassung der Versprechen

Ein Versprechen ist ein Wert über die Zeit. Versprechungen haben Status, sie beginnen als ausstehend ohne Wert und können sich auf Folgendes beschränken:

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

Ein Versprechen kann nur den Status ändern einmal danach bleibt es immer für immer auf dem gleichen Status. Sie können then Handler an Versprechen anhängen, um deren Wert zu extrahieren und Fehler zu behandeln. then Handler erlauben Verketten von Anrufen. Versprechungen werden durch nter Verwendung von APIs, die sie zurückgeben erstellt. Zum Beispiel verspricht der modernere AJAX Ersatz fetch oder jQueries $.get die Rückkehr.

Wenn wir .then auf ein Versprechen anrufen und zurück etwas daraus - wir bekommen ein Versprechen für den verarbeiteten Wert. Wenn wir ein anderes 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 Versprechenszustände von oben demonstrieren, indem Sie den Versprechenskonstruktor 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 konvertiert haben, um Versprechen zu verwenden, können wir then verwenden, 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;
});

Anstatt value zurückzugeben, was wir aufgrund des Parallelitätsmodells nicht können, geben wir wrapper für einen Wert zurück, den wir auspacken können mit then. Es ist wie eine Box, die Sie mit then öffnen können.

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 aus 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. Hierbei handelt es sich um 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 1,2,3,3,3,3,.... zurückgibt, die iteriert werden kann. Obwohl dies für sich genommen interessant ist und viel Raum für Möglichkeiten eröffnet, gibt es einen besonderen interessanten Fall.

Wenn es sich bei der von uns produzierten Sequenz um eine Folge von Aktionen und nicht um Zahlen handelt, können wir die Funktion anhalten, wenn eine Aktion ausgeführt wird, und auf sie warten, bevor wir die Funktion wieder aufnehmen. Anstelle einer Folge von Zahlen brauchen wir also eine Folge von Zukunft ​​Werten - 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 kurzen Codezeile würde den Rahmen dieser Antwort sprengen. Ich werde hier Bluebirds Promise.coroutine verwenden, aber es gibt auch andere Wrapper wie co oder 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 ein Versprechen an sich zurück, das wir von anderen Koroutinen 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. Derzeit gibt es mehrere Vorschläge, aber in allen können Sie await versprechen. Dies ist nur "Zucker" (schönere Syntax) für den obigen ES6-Vorschlag, indem die Schlüsselwörter async und await hinzugefügt werden. Das obige Beispiel machen:

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
}

Trotzdem gibt es immer noch ein Versprechen :)

303

Sie verwenden Ajax falsch. Die Idee ist, dass es nichts zurückgibt, sondern die Daten an eine sogenannte Rückruffunktion übergibt, 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 im Submit-Handler etwas zurückgeben, wird nichts ausgeführt. Sie müssen die Daten stattdessen entweder übergeben oder direkt in der Erfolgsfunktion damit tun, was Sie wollen.

238
Nic

Die einfachste Lösung besteht darin, eine JavaScript-Funktion zu erstellen und diese für den Ajax-Rückruf success 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);    
}); 
225
Hemant Bavle

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

enter image description here

206

Angular1

Wenn Sie AngularJS verwenden, können Sie mit Promises mit dieser Situation umgehen.

hier heißt es,

Versprechungen können verwendet werden, um asynchrone Funktionen zu entstellen, und ermöglichen die Verkettung mehrerer Funktionen.

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

Beispiel gefunden in docs unten erwähnt.

  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 später

Schauen Sie sich in Angular2 das folgende Beispiel an, aber es ist empfohlen , Observables mit Angular2 zu verwenden.

 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 verbrauchen,

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

Siehe den original Post hier. TypeScript unterstützt jedoch nicht native es6-Versprechen , wenn Sie es verwenden möchten, benötigen Sie möglicherweise ein Plugin dafür.

Zusätzlich hier ist das Versprechen spec hier definieren.

151

Die meisten Antworten hier geben nützliche Vorschläge für einzelne asynchrone Vorgänge. Manchmal wird dies jedoch angezeigt, wenn Sie jeweils einen asynchronen Vorgang ausführen müssen Eintrag in ein Array oder eine andere listenartige Struktur. 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:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.Push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

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

Der Grund, warum dies nicht funktioniert, ist, dass die Rückrufe von doSomethingAsync noch nicht ausgeführt wurden, als Sie versuchen, die Ergebnisse zu verwenden.

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

Parallel

Sie können alle von ihnen starten und nachverfolgen, 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:

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

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

(Wir könnten auf expecting verzichten und nur results.length === theArray.length verwenden, aber das lässt uns die Möglichkeit offen, dass theArray geändert wird, während die Anrufe ausstehen ...)

Beachten Sie, wie wir index aus forEach verwenden, um das Ergebnis in results an der gleichen Position wie der betreffende Eintrag zu speichern, auch wenn die Ergebnisse nicht in der Reihenfolge eintreffen, in der sie gestartet wurden (da asynchrone Aufrufe nicht unbedingt in der Reihenfolge abgeschlossen werden müssen) ).

Aber was ist, wenn Sie diese Ergebnisse aus einer Funktion zurückgeben müssen ? Wie die anderen Antworten gezeigt haben, können Sie nicht; Sie müssen Ihre Funktion annehmen und einen Rückruf anfordern (oder ein Versprechen zurückgeben). Hier ist eine Callback-Version:

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:

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

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

Oder hier ist eine Version, die stattdessen einen Promise 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 doSomethingAsync uns Fehler übergeben würde, würden wir reject verwenden, um das Versprechen abzulehnen, wenn wir einen Fehler erhalten.)

Beispiel:

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

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

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

Wenn doSomethingAsync Ihnen ein Versprechen gibt, können Sie Promise.all verwenden:

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, dass doSomethingAsync ein zweites und drittes Argument ignoriert, können Sie es einfach direkt an map übergeben (map ruft seinen Rückruf mit drei Argumenten auf, aber die meisten Leute verwenden nur das erste die meiste Zeit):

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

Beispiel:

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

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

Beachten Sie, dass Promise.all sein Versprechen mit einer Reihe der Ergebnisse aller Versprechen auflöst, die Sie ihm geben, wenn sie alle aufgelöst sind, oder sein Versprechen ablehnt, wenn das erste von den Versprechungen, die Sie geben, lehnt es ab.

Serie

Angenommen, Sie möchten nicht, dass die Operationen parallel ablaufen? Wenn Sie sie nacheinander ausführen möchten, müssen Sie warten, bis die einzelnen Vorgänge abgeschlossen sind, bevor Sie mit dem nächsten Vorgang beginnen. Hier ist ein Beispiel für eine Funktion, die das macht 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 Serien ausführen, können wir einfach results.Push(result) verwenden, da wir wissen, dass die Ergebnisse nicht in der falschen Reihenfolge angezeigt werden. Oben hätten wir results[index] = result; verwenden können. In einigen der folgenden Beispiele steht jedoch kein Index zur Verfügung.)

Beispiel:

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

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

(Oder erstellen Sie erneut einen Wrapper für doSomethingAsync, der Ihnen ein Versprechen gibt und die folgenden Schritte ausführt ...)

Wenn doSomethingAsync Ihnen ein Versprechen gibt, wenn Sie ES2017 + -Syntax verwenden können (möglicherweise mit einem Transpiler wie Babel ), können Sie eine async -Funktion mit for-of und 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:

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

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

Wenn Sie die ES2017 + -Syntax (noch) nicht verwenden können, können Sie eine Variation des "Promise Reduce" -Musters verwenden (dies ist komplexer als das übliche Promise Reduce, da wir es sind) nicht das Ergebnis von einem zum nächsten weitergeben, sondern die Ergebnisse in einem Array zusammenfassen):

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:

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

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

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

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

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

Sehen 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, gibt getJoke ein gelöstes Versprechen zurück (Es wird behoben, wenn res.data.value zurückgegeben wird.) Sie warten also, bis die Anforderung $ http.get abgeschlossen ist, und dann console.log (res.joke) wird ausgeführt (als normaler asynchroner Ablauf).

Dies ist der 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);
  });
})();
104

Ein anderer Ansatz, um einen Wert von einer asynchronen Funktion zurückzugeben, ist die Übergabe eines Objekts, 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 Objekt result, um den Wert während der asynchronen Operation zu speichern. Dies ermöglicht, dass das Ergebnis auch nach dem asynchronen Job verfügbar ist.

Ich benutze diesen Ansatz oft. Mich würde interessieren, wie gut dieser Ansatz funktioniert, wenn es darum geht, das Ergebnis durch aufeinanderfolgende Module zu verbinden.

92
jsbisht

Dies ist einer der Orte, an denen Datenbindung auf zwei Arten, der in vielen neuen JavaScript-Frameworks verwendet wird, für Sie hervorragend funktioniert ...

Wenn Sie also Angular, React oder ein anderes Framework verwenden, das Datenbindung auf zwei Arten oder Speicherkonzept funktioniert, wird dieses Problem einfach für Sie behoben. In einfachen Worten ist Ihr Ergebnis undefined in der ersten Phase, Sie haben also result = undefined, bevor Sie die Daten erhalten. Sobald Sie das Ergebnis erhalten, werden sie aktualisiert und zugewiesen der neue wert welcher antwort deines ajax aufrufs ...

Aber wie kann man das in reinen Javascript oder jQuery zum Beispiel so machen, wie man es in dieser Frage gestellt hat?

Sie können ein Rückruf, Versprechen und kürzlich Beobachtbar verwenden, um dies für Sie zu erledigen. In Versprechen haben wir beispielsweise Funktionen wie success() oder then(), die wird ausgeführt, wenn Ihre Daten für Sie bereit sind, gleich mit Rückruf oder abonnieren Funktion an 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 unter Versprechen und Observablen, die neuere Methoden zum Ausführen dieser asynchronen Aufgaben darstellen.

91
Alireza

Während Versprechungen 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 async1 durchlaufen; überprüfen Sie, ob name undefiniert ist oder nicht, und rufen Sie den Rückruf entsprechend auf.

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

async1(name, async2)

Obwohl 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 auschecken hier .

83
rohithpr

Kurze Antwort ist 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
});
78

Das folgende Beispiel zeigt, wie es geht

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

Dieses Arbeitsbeispiel ist in sich abgeschlossen. Es wird ein einfaches Anforderungsobjekt definiert, das das Fensterobjekt XMLHttpRequest verwendet, um Anrufe zu tätigen. Es wird eine einfache Funktion definiert, die darauf wartet, dass einige Versprechen erfüllt werden.

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

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

Für jeden Gegenstand wird durch ein neues Versprechen ein Block ausgelöst - ExecutionBlock, das Ergebnis analysiert, eine neue Reihe von Versprechungen basierend auf dem Ergebnisarray geplant, dh eine Liste von Spotify user -Objekten, und das neue ausgeführt HTTP-Aufruf innerhalb von ExecutionProfileBlock asynchron.

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

NOTE Aktuelle Spotify search-APIs erfordern die Angabe eines Zugriffstokens in den Anforderungsheadern:

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

Damit Sie das folgende Beispiel ausführen können, 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 ausführlich besprochen hier .

76
loretoparisi

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

Das ist ganz einfach:

  • Ein Versprechen zurückgeben
  • Verwenden Sie 'await' , um JavaScript anzuweisen, 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)

})()

wait wird in allen aktuellen Browsern und auf Knoten 8 unterstützt

72
mikemaccana

Sie können diese benutzerdefinierte Bibliothek (mit Promise geschrieben) 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 Anwendungsbeispiel:

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

Js ist ein einzelner Thread.

Der Browser kann in drei Teile unterteilt werden:

1) Ereignisschleife

2) Web API

3) Ereigniswarteschlange

Event Loop läuft für immer, dh eine Art Endlosschleife. In der Event Queue werden alle Ihre Funktionen auf ein Ereignis verschoben (Beispiel: Klicken). Dies wird nacheinander aus der Warteschlange ausgeführt und in die Event Loop gestellt, die diese Funktion ausführt und selbst vorbereitet Das bedeutet, dass die Ausführung einer Funktion erst startet, wenn die Funktion, bevor sie in der Warteschlange steht, in der Ereignisschleife ausgeführt wird.

Angenommen, wir haben zwei Funktionen in eine Warteschlange gestellt. Eine dient zum Abrufen von Daten vom Server und eine andere verwendet diese Daten. Wir haben die Funktion serverRequest () zuerst in die Warteschlange gestellt und dann die Funktion utiliseData (). Die serverRequest-Funktion wechselt in die Ereignisschleife und ruft den Server an, da wir nie wissen, wie viel Zeit zum Abrufen von Daten vom Server benötigt wird. Daher wird dieser Prozess voraussichtlich einige Zeit in Anspruch nehmen und wir beschäftigen uns mit unserer Ereignisschleife und hängen unsere Seite, an der Stelle, an der das Web ist Die API übernimmt diese Funktion aus der Ereignisschleife und kümmert sich darum, dass der Server 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, da jedoch 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 serverRequest () -Funktion 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 verschoben. Da es am Ende der Warteschlange gepusht wird, können wir seine Daten nicht verwenden, da in unserer Warteschlange keine Funktion mehr vorhanden ist, um diese Daten zu verwenden. Daher ist es nicht möglich, etwas von Async Call zurückzugeben.

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

Ein Bild von einer der Antworten hier. Erklärt die Verwendung von Rückrufen korrekt ... Wir geben unsere Funktion (Funktion, die vom Server zurückgegebene Daten verwendet) an die Funktion, die den Server anruft.

CallBack

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

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


  }
  xmlHttpReq.send(null);

}

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

Lesen Sie hier die neuen Methoden in ECMA (2016/17) für asynchrone Anrufe (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

59
Aniket Jha

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

Wenn die zugrunde liegende Funktion zugesagt ist

nsynjs wertet alle Versprechungen nacheinander aus und schreibt das Versprechungsergebnis in die Eigenschaft data:

function synchronousCode() {

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

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

Wenn die zugrunde liegende Funktion nicht zugesagt wird

Schritt 1. Wrap-Funktion mit Rückruf in nsynjs-fähigen Wrapper (falls er eine versprochene Version hat, 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. Setzen Sie die synchrone Logik in Funktion:

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 schrittweise aus und pausiert die Ausführung, falls das Ergebnis einer langsamen Funktion nicht bereit ist.

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

59
amaksr

Es ist ein sehr häufiges Problem, mit dem wir konfrontiert sind, wenn wir uns mit den 'Geheimnissen' von JavaScript auseinandersetzen. Lassen Sie mich versuchen, dieses Rätsel heute 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 Zeile in Folge mit ihrem Job fertig ist), und das Ergebnis ist das gleiche wie erwartet.

Fügen wir nun eine kleine Wendung hinzu, indem wir unsere Funktion mit einer kleinen Verzögerung versehen, damit nicht alle Codezeilen der Reihe nach "fertig" sind. Somit wird das asynchrone Verhalten der Funktion emuliert:

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

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

Also los geht's, diese Verzögerung hat einfach die von uns erwartete Funktionalität zerstört! 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 undefined), startet jedoch einen Timer, der nach 1s eine Funktion ausführt, um 'wohoo' zurückzugeben. Aber wie Sie sehen, ist der Wert, der bar zugewiesen wird, das sofort zurückgegebene Material von foo () und nichts anderes, das später kommt.

Wie gehen wir dieses Problem an?

Fragen wir unsere Funktion nach einem VERSPRECHEN. Bei Versprechen geht es wirklich darum, was es bedeutet: Es bedeutet, dass die Funktion Ihnen garantiert, dass Sie alle zukünftigen Ausgaben liefern können. 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. in Angriff zu nehmen, können Sie ein Versprechen verwenden, um den Wert (den Sie zurückgeben möchten) zu resolve. Kurz gesagt, Sie Auflösung Wert anstelle von Rückgabe, in asynchronen Funktionen.

UPDATE (Verspricht mit async/await)

Abgesehen von der Verwendung von then/catch, um mit Versprechungen zu arbeiten, gibt es noch einen weiteren Ansatz. Die Idee ist, eine asynchrone Funktion zu erkennen und dann auf die Versprechen zu warten zu lösen, bevor Sie zur nächsten Codezeile übergehen. Es ist immer noch nur das promises unter der Haube, aber mit einem anderen syntaktischen Ansatz. Zur Verdeutlichung finden Sie nachfolgend einen Vergleich:

dann/catch version:

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

async/wait version:

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

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

So führen Sie den obigen Code aus:

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

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

Die Rückrufe ...args werden in ein Array eingeschlossen und beim Lesen zerstört, sodass das Muster Rückrufe verarbeiten kann, die mehrere Argumente enthalten. Zum Beispiel mit Knoten fs :

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

Hier einige Ansätze zum Arbeiten mit asynchronen Anforderungen:

  1. Browser Versprechen Objekt
  2. Q - Eine vielversprechende Bibliothek für JavaScript
  3. A + Promises.js
  4. jQuery verzögert
  5. XMLHttpRequest API
  6. Callback-Konzept 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();
37
Mohan Dere

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

In diesem Thread wurden mehrere Lösungen angegeben. Am einfachsten ist es möglicherweise, ein Objekt an die Methode foo() zu übergeben und die Ergebnisse nach Abschluss des asynchronen Aufrufs in einem Element 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 jetzt jedoch in result.response gespeichert.

35
David R Tribble

Verwenden Sie eine Funktion callback() innerhalb des Erfolgs foo(). 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();
34
Mahfuzur Rahman

Wir befinden uns in einem Universum, das sich in einer Dimension fortzubewegen scheint, die wir "Zeit" nennen. Wir verstehen nicht wirklich, wie spät es ist, aber wir haben Abstraktionen und Vokabeln entwickelt, mit denen wir darüber nachdenken und sprechen können: "Vergangenheit", "Gegenwart", "Zukunft", "Vorher", "Nachher".

Die Computersysteme, die wir bauen, haben immer mehr Zeit als wichtige Dimension. Bestimmte Dinge sind für die Zukunft vorbereitet. Dann müssen andere Dinge passieren, nachdem diese ersten Dinge schließlich eintreten. Dies ist der Grundbegriff "Asynchronität". In unserer zunehmend vernetzten Welt wartet der häufigste Fall von Asynchronität darauf, dass ein entferntes 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 legen. Sie können die Milch momentan nicht in Ihren Kaffee geben, da sie noch nicht da ist. Sie müssen warten, bis es kommt, bevor Sie es in Ihren Kaffee geben. Mit anderen Worten, das Folgende wird nicht funktionieren:

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

Weil JS nicht wissen kann, dass es wait muss, damit order_milk beendet wird, bevor put_in_coffee ausgeführt wird. Mit anderen Worten, es ist nicht bekannt, dass order_milk asynchron - ist, was erst in der Zukunft zu Milch führen wird. JS und andere deklarative Sprachen führen eine Anweisung nach der anderen aus, ohne zu warten.

Der klassische JS-Ansatz für dieses Problem besteht darin, unter Ausnutzung der Tatsache, dass JS Funktionen als erstklassige Objekte unterstützt, die weitergegeben werden können, eine Funktion als Parameter an die asynchrone Anforderung zu übergeben, die dann aufgerufen wird, wenn sie abgeschlossen ist 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, put_in_coffee auf.

Das Problem bei diesem Callback-Ansatz ist, dass er die normale Semantik einer Funktion verschmutzt, die ihr Ergebnis mit return meldet. Stattdessen dürfen Funktionen ihre Ergebnisse nicht durch Aufrufen eines als Parameter angegebenen Rückrufs melden. Außerdem kann dieser Ansatz bei längeren Ereignissequenzen schnell unhandlich werden. Nehmen wir zum Beispiel an, ich möchte warten, bis die Milch in den Kaffee gelangt, 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 an put_in_coffee übergebe, sowohl die Milch, die hineingegeben werden soll, als auch die Aktion (drink_coffee), die ausgeführt werden soll, sobald die Milch hineingegeben wurde. Solcher Code wird schwer zu schreiben und zu lesen und 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);
}

Geben Sie Versprechen ein

Dies war die Motivation für die Vorstellung eines "Versprechens", bei dem es sich um einen bestimmten Wertetyp handelt, der ein future oder asynchronous darstellt Ergebnis irgendeiner Art. Es kann etwas darstellen, das bereits geschehen ist oder das in Zukunft geschehen wird oder das möglicherweise überhaupt nicht mehr geschehen wird. Promises haben eine einzige Methode mit dem Namen then, an die Sie eine Aktion übergeben, die ausgeführt werden soll, wenn das Ergebnis, das das Promise darstellt, realisiert wurde.

Im Fall unserer Milch und unseres Kaffees entwerfen wir order_milk, um ein Versprechen für die ankommende Milch zurückzugeben, und geben dann put_in_coffee als then Aktion wie folgt an:

order_milk() . then(put_in_coffee)

Ein Vorteil davon ist, dass wir diese aneinander reihen können, um Sequenzen zukünftiger Vorkommen 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 ist alles, was wir getan haben, ein return zum Aufruf von $.ajax hinzugefügt. Dies funktioniert, weil jQueries $.ajax bereits eine Art versprechensähnliches Ergebnis liefert. (In der Praxis würden wir diesen Aufruf, ohne auf Einzelheiten einzugehen, lieber so einschließen, dass er ein echtes Versprechen enthält, oder eine Alternative zu $.ajax verwenden, die dies tut.) Nun, wenn wir die Datei laden und warten möchten Damit es zu Ende geht und dann etwas tut, können wir einfach sagen

get_data() . then(do_something)

zum Beispiel,

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

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

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

Das Schlüsselwort async

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

a();
b();

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

a() . then(b);

Oben haben wir gesagt, "JS hat keine Möglichkeit zu wissen, dass es wait braucht, um den ersten Aufruf zu beenden, bevor es den zweiten ausführt". Wäre es nicht schön, wenn es war eine Möglichkeit gäbe, JS das mitzuteilen? Es stellt sich heraus, dass es das Schlüsselwort await gibt, das in einer speziellen Art von Funktion verwendet wird, die als "asynchrone" Funktion bezeichnet wird. Diese Funktion ist Teil der kommenden Version von ES, ist jedoch bereits in Transpilern wie Babel verfügbar, sofern die richtigen Voreinstellungen festgelegt wurden. So können wir einfach schreiben

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

In deinem Fall könntest du so etwas schreiben

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

Natürlich gibt es viele Ansätze wie synchrone Anfragen, aber aus meiner Erfahrung denke ich, dass Sie den Callback-Ansatz verwenden sollten. Es ist natürlich zu asynchronem Verhalten von Javascript. So kann Ihr Code-Snippet ein wenig anders geschrieben werden:

function foo() {
    var result;

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

    return result;
}

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

Die Frage war:

Wie kann ich die Antwort von einem asynchronen Anruf zurückgeben?

was interpretiert werden kann als:

Wie kann man asynchronen Code synchron aussehen lassen ?

Die Lösung besteht darin, Rückrufe zu vermeiden und eine Kombination aus Promises und async/await 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.)

Ermöglichen Sie zunächst die Verwendung von JQuery, damit $ als S verfügbar ist:

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

Definieren Sie eine Funktion, die ein Promise 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, 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")
26

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 individuelles 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 bis unten js-Compiler zu implementieren, falls diese nicht definiert ist. Fügen Sie diesen Code einfach vor Ihrem Hauptcode ein und verwenden Sie 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;
    }();
}
20
Amir Forsati

Anstatt Sie mit Code zu bewerfen, gibt es zwei Konzepte, mit denen Sie verstehen, wie JS mit Rückrufen und Asynchronität umgeht. (Ist das überhaupt ein Wort?)

Das Ereignisschleifen- und Nebenläufigkeitsmodell

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

In groben Zügen betrachtet ist die Ereignisschleife wie der Projektmanager. Sie wartet ständig auf alle Funktionen, die ausgeführt werden sollen, und kommuniziert zwischen der Warteschlange und dem Stapel.

_while (queue.waitForMessage()) {
   queue.processNextMessage();
}
_

Sobald er eine Nachricht zum Ausführen von etwas erhält, fügt er es der Warteschlange hinzu. Die Warteschlange ist die Liste der Dinge, die darauf warten, ausgeführt zu werden (wie Ihre AJAX -Anforderung). 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 heißt es also foobarFunc aufrufen.

_function foobarFunc (var) {
  console.log(anotherFunction(var));
}
_

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

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

WANN läuft etwas

Wenn Sie mit AJAX einen Anruf bei einem externen Teilnehmer tätigen oder einen beliebigen asynchronen Code ausführen (z. B. setTimeout), ist Javascript auf eine Antwort angewiesen, bevor der Vorgang fortgesetzt werden kann.

Die große Frage ist, wann die Antwort kommt. Die Antwort ist, dass wir es nicht wissen. Die Ereignisschleife wartet also darauf, dass die Meldung "hey run me" lautet. Wenn JS nur synchron auf diese Nachricht gewartet hat, friert Ihre App ein und es wird stören. JS führt also das nächste Element in der Warteschlange aus, während es darauf wartet, dass die Nachricht wieder zur Warteschlange hinzugefügt wird.

Deshalb verwenden wir bei asynchroner Funktionalität sogenannte Callbacks . Es ist ein bisschen wie ein Versprechen ganz wörtlich. Wie ich verspreche, irgendwann etwas zurückzugeben , verwendet jQuery bestimmte Rückrufe, die _deffered.donedeffered.fail_ und _deffered.always_ (unter anderem) genannt werden. Sie können sie alle sehen hier

Sie müssen also eine Funktion übergeben, deren Ausführung mit den übergebenen Daten zu einem bestimmten Zeitpunkt versprochen wird.

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

also wirst du die meiste Zeit (aber nicht immer) foo nicht foo()

Hoffentlich ergibt das einen Sinn. Wenn Sie auf verwirrend wirkende Dinge stoßen, empfehle ich dringend, die Dokumentation vollständig zu lesen, um zumindest ein Verständnis dafür zu erlangen. Das macht Sie zu einem viel besseren Entwickler.

16
Matthew Brent

Mit ES2017 sollten Sie dies als Funktionsdeklaration haben

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

Und es so auszufü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)

})
15

Lassen Sie uns zuerst den Wald sehen, bevor wir uns die Bäume ansehen.

Es gibt hier viele informative Antworten mit tollen Details, ich werde keine davon wiederholen. Der Schlüssel zur Programmierung in JavaScript ist zunächst das korrektes mentales Modell der Gesamtausführung.

  1. Ihre Einstiegspunkte werden als Ergebnis eines Ereignisses ausgeführt. Beispielsweise wird ein Skript-Tag mit Code in den Browser geladen. (Aus diesem Grund müssen Sie sich möglicherweise mit der Bereitschaft der Seite befassen, Ihren Code auszuführen, wenn zuerst dom-Elemente erstellt werden müssen usw.)
  2. Ihr Code wird vollständig ausgeführt - unabhängig davon, wie viele asynchrone Aufrufe ausgeführt werden -, ohne dass beliebige Ihrer Rückrufe ausgeführt werden, einschließlich XHR-Anforderungen, festgelegter Zeitüberschreitungen, dom-Ereignishandlern usw. Jeder dieser Rückrufe wartet darauf, ausgeführt zu werden sitzen in einer Warteschlange und warten darauf, dass sie an die Reihe kommen, nachdem alle anderen Ereignisse, die ausgelöst wurden, die Ausführung beendet haben.
  3. Jeder einzelne Rückruf auf eine XHR-Anforderung, das Festlegen einer Zeitüberschreitung oder das Festlegen des Ereignisses nach dem Aufrufen wird dann vollständig ausgeführt.

Die gute Nachricht ist, dass Sie sich niemals um die Rennbedingungen sorgen müssen, wenn Sie diesen Punkt gut verstehen. Sie sollten in erster Linie festlegen, wie Sie Ihren Code als Antwort auf verschiedene diskrete Ereignisse organisieren und wie Sie sie zu einer logischen Sequenz zusammenführen möchten. Sie können Versprechungen oder neue Async/Warten auf höherer Ebene als Werkzeuge verwenden, um dies zu erreichen, oder Sie können Ihre eigenen würfeln.

Sie sollten jedoch keine taktischen Tools verwenden, um ein Problem zu lösen, bis Sie mit der eigentlichen Problemdomäne vertraut sind. Zeichnen Sie eine Karte dieser Abhängigkeiten, um zu wissen, was wann ausgeführt werden muss. Ein Ad-hoc-Ansatz für all diese Rückrufe wird Ihnen einfach nicht weiterhelfen.

14
Haim Zamir

Hier ist ein Beispiel, das funktioniert:

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

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

Die Anforderung funktioniert nicht synchron, sodass Sie die Daten nicht als typischen Code synchron lesen können. Mit async/await können Sie jedoch einen asynchronen Code erstellen, der dem Synchronisierungscode sehr ähnlich sieht. Code, dessen Prozessanforderungsdaten von einer asynchronen Funktion umbrochen werden müssen (load im unteren Ausschnitt) und in dem Sie await Schlüsselwort vor foo() hinzufügen müssen (die auch async/await verwenden) ).

async function foo() {
  var url= 'https://jsonplaceholder.typicode.com/todos/1';
  var result= await (await fetch(url)).text(); // or .json()
  return result;
}

async function load() {
  var result = await foo();
  console.log(result);
}

load();
5

verwenden Sie async/await mit einem Transpiler wie Babel, um es in älteren Browsern zum Laufen zu bringen. Sie müssen dieses Babel-Preset und die Polyfüllung auch von npm: npm i -D babel-preset-env babel-polyfill installieren.

function getData(ajaxurl) { 
  return $.ajax({
    url: ajaxurl,
    type: 'GET',
  });
};

async test() {
  try {
    const res = await getData('https://api.icndb.com/jokes/random')
    console.log(res)
  } catch(err) {
    console.log(err);
  }
}

test();

oder der Rückruf .then ist nur eine andere Möglichkeit, dieselbe Logik zu schreiben.

getData(ajaxurl).then(function(res) {
    console.log(res)
}
1
Murtaza Hussain