javascript variable Wie gebe ich die Antwort von einem asynchronen Anruf zurück?




return type of ajax call (24)

Wir befinden uns in einem Universum, das sich scheinbar entlang einer Dimension zu entwickeln scheint, die wir "Zeit" nennen. Wir verstehen nicht wirklich, was die Zeit ist, aber wir haben Abstraktionen und Vokabeln entwickelt, die es uns erlauben, darüber nachzudenken und darüber zu sprechen: "Vergangenheit", "Gegenwart", "Zukunft", "Vorher", "Nach".

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 geschehen. Dies ist der Grundbegriff, der als "Asynchronität" bezeichnet wird. In unserer zunehmend vernetzten Welt wartet der häufigste Fall der 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 geben. Sie können die Milch jetzt nicht in Ihren Kaffee geben, da sie noch nicht hier ist. Sie müssen warten, bis es kommt, bevor Sie es in Ihren Kaffee geben. Mit anderen Worten funktioniert das Folgende nicht:

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

Da JS hat keine Möglichkeit , zu wissen , dass es muss warten für order_milkbeenden , bevor sie ausgeführt wird put_in_coffee. Mit anderen Worten, weiß er nicht , dass order_milkist asynchron --is etwas , das in der Milch gehen zu führen , bis dereinst nicht. JS und andere deklarative Sprachen führen eine Anweisung nach der anderen aus, ohne zu warten.

Der klassische Ansatz von JS zu diesem Problem, der die Tatsache, dass JS Funktionen als erstklassige Objekte unterstützt, die herumgereicht werden können, nutzt, besteht darin, 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_milkLos geht's, bestellt die Milch, dann, wann und nur wenn sie ankommt, ruft sie put_in_coffee.

Das Problem bei diesem Rückrufansatz besteht darin, dass die normale Semantik einer Funktion, die ihr Ergebnis meldet, mit verschmutzt wird return. Funktionen müssen stattdessen ihre Ergebnisse melden, indem sie einen als Parameter angegebenen Rückruf aufrufen. Dieser Ansatz kann auch bei längeren Ereignissequenzen schnell unhandlich werden. Nehmen wir beispielsweise an, ich möchte warten, bis die Milch in den Kaffee gefüllt wird, und dann und nur dann einen dritten Schritt ausführen, nämlich den Kaffee zu trinken. Ich muss am Ende so etwas schreiben:

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

wo ich put_in_coffeedie Milch übergeben werde, um sie hineinzulegen, und auch die Aktion ( drink_coffee), die ausgeführt wird, sobald die Milch eingelegt ist. Solcher Code wird schwer zu schreiben, zu lesen und zu debuggen.

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

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

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

Versprechen eingeben

Dies war die Motivation für die Vorstellung eines "Versprechens", bei dem es sich um einen bestimmten Werttyp handelt, der ein zukünftiges oder asynchrones Ergebnis darstellt. Es kann etwas darstellen, das bereits geschehen ist oder in Zukunft passieren wird oder überhaupt nicht passieren wird. Versprechungen haben eine einzige, benannte Methode, thenan die Sie eine Aktion übergeben, die ausgeführt werden soll, wenn das Ergebnis, das das Versprechen darstellt, realisiert wurde.

Im Fall unserer Milch und unseres Kaffees möchten wir order_milkIhnen ein Versprechen für die ankommende Milch zurückgeben und dann put_in_coffeeals thenAktion angeben :

order_milk() . then(put_in_coffee)

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

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 packen, die ein Versprechen zurückgibt:

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

Alles, was wir getan haben, ist returndem Aufruf an a hinzuzufügen $.ajax. Das funktioniert, weil jQuery $.ajaxbereits eine Art versprechender Sache zurückgibt. (In der Praxis würden wir es vorziehen, diesen Aufruf zu verpacken, um ein echtes Versprechen abzugeben, oder eine Alternative dazu zu $.ajaxverwenden.) Nun, wenn wir die Datei laden und warten möchten, bis sie beendet ist und dann etwas tun, können wir einfach sagen

get_data() . then(do_something)

zum Beispiel,

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

Bei der Verwendung von Versprechen geben wir am Ende viele Funktionen weiter. Daher thenist es oft hilfreich, die kompakteren Pfeilfunktionen im ES6-Stil zu verwenden:

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

Das asyncSchlüsselwort

Es ist jedoch immer noch etwas unzufrieden, Code auf eine Weise zu schreiben, wenn er synchron ist, und eine ganz andere Art, wenn er asynchron ist. Für synchron schreiben wir

a();
b();

Wenn aaber asynchron, mit Versprechen müssen wir schreiben

a() . then(b);

Oben sagten wir "JS hat keine Möglichkeit zu wissen, dass es warten muss, bis der erste Anruf beendet ist, bevor der zweite ausgeführt wird". Wäre es nicht schön, wenn es ist eine Möglichkeit , dass JS zu erzählen? Es stellt sich heraus, dass es - das awaitSchlüsselwort gibt, das innerhalb eines speziellen Funktionstyps verwendet wird, der als "async" -Funktion bezeichnet wird. Diese Funktion ist Teil der kommenden Version von ES, ist jedoch bereits in Transpillern wie Babel mit den richtigen Voreinstellungen verfügbar. Dies erlaubt uns einfach zu schreiben

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

In Ihrem Fall könnten Sie so etwas schreiben

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

Ich habe eine Funktion foo , die eine Ajax-Anfrage macht. Wie kann ich die Antwort von foo ?

Ich habe versucht, den Wert aus dem success sowie die Antwort einer lokalen Variablen in der Funktion zuzuweisen und diese zurückzugeben, aber keine dieser Methoden 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`.

Natürlich gibt es viele Ansätze wie synchrones Anfordern, Versprechen, aber aus meiner Erfahrung sollten Sie den Callback-Ansatz verwenden. Es ist natürlich für asynchrones Verhalten von Javascript. Ihr Code-Snippet kann also etwas anders geschrieben werden:

function foo() {
    var result;

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

    return result;
}

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

Die einfachste Lösung ist das Erstellen einer JavaScript-Funktion und das Aufrufen des Ajax- successCallbacks.

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

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

Wenn die zugrunde liegende Funktion versprochen wird

nsynjs wird alle Versprechungen nacheinander bewerten und das Versprechungsergebnis in die dataEigenschaft einfügen :

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 zugrunde liegende Funktion nicht zugesagt wird

Schritt 1. Funktion mit Callback in nsynjs-aware Wrapper umschließen (wenn die Version versprochen wurde, können Sie diesen Test ü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 die Funktion:

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

Schritt 3. Funktion synchron über nnsynjs ausführen:

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

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

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


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

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

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

Um den obigen Code auszuführen, führen Sie Folgendes 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öchten, die ES6 nicht unterstützen, können Sie den Code über Babel oder den Schließungscompiler ausführen, um ECMAScript 5 zu generieren.

Der Callback ...argswird in ein Array eingeschlossen und beim Lesen zerstört, damit das Muster Callbacks mit mehreren Argumenten verarbeiten kann. Zum Beispiel mit dem Knoten fs :

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

Dies ist einer der Bereiche, an denen zwei Arten der Datenbindung , die in vielen neuen JavaScript-Frameworks verwendet werden, für Sie von großem Nutzen sind ...

Wenn Sie also Angular, React oder andere Frameworks verwenden, die zwei Arten der Datenbindung verwenden, ist dieses Problem für Sie einfach behoben. In einfachen Worten, Ihr Ergebnis befindet sich undefinedim ersten Schritt. Sie haben also, result = undefinedbevor Sie die Daten erhalten. Sobald Sie das Ergebnis erhalten, wird es aktualisiert und dem neuen Wert zugewiesen, der als Antwort auf Ihren Ajax-Aufruf gilt.

Aber wie können Sie es in reinem Javascript oder jQuery zum Beispiel tun, wie Sie es in dieser Frage stellten?

Sie können einen verwenden Rückruf , versprechen und vor kurzem beobachtbar es für Sie zu behandeln, zum Beispiel in Versprechen , die wir eine Funktion wie Erfolg haben () oder dann () , die ausgeführt wird, wenn die Daten für Sie bereit, das gleiche mit Rückruf oder abonnieren Funktion auf 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 Versprechungen und Observablen, die neuere Wege sind, um async Sachen zu machen.


Während Versprechungen und Rückrufe in vielen Situationen gut funktionieren, ist es ein Schmerz im Rücken, etwas auszudrücken:

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

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

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

async1(name, async2)

In kleinen Beispielen ist es in Ordnung, aber es ist ärgerlich, wenn Sie viele ähnliche Fälle und Fehlerbehandlung 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 here auschecken .


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

Mit ES2017 sollten Sie dies als Funktionsdeklaration haben

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

Und es so ausführen.

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

Oder die Versprechen-Syntax

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

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

})

Angular1

Für AngularJS , die AngularJS , kann diese Situation mit verwendet werden Promises.

Here heißt es:

Versprechungen können verwendet werden, um asynchrone Funktionen zu verwirren und ermöglichen es, mehrere Funktionen miteinander zu verketten.

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

Beispiel in den unten genannten docs .

  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

In Angular2mit Blick auf das folgende Beispiel, aber seine recommended zu verwenden Observablesmit Angular2.

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

}

Sie können das auf diese Weise konsumieren,

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

Siehe den original Beitrag hier. TypScript unterstützt jedoch keine nativen es6 Promises . Wenn Sie es verwenden möchten, benötigen Sie dazu möglicherweise ein Plugin.

Zusätzlich werden hier die Versprechen spec .


→ Eine allgemeinere Erklärung des asynchronen Verhaltens mit verschiedenen Beispielen finden Sie unter Warum ist 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 nachstehenden Lösungen fort.

Das Problem

Das A in Ajax steht für asynchronous . Das heißt, das Senden der Anfrage (bzw. das Empfangen der Antwort) wird aus dem normalen Ausführungsablauf genommen. In Ihrem Beispiel gibt $.ajax sofort zurück und die nächste Anweisung gibt das return result; , wird ausgeführt, bevor die Funktion, die Sie als success sogar aufgerufen wurde.

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

Synchron

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

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

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

var item = findItem();

// Do something with item
doSomethingElse();

Obwohl die findItem sehr lange dauern kann, wird jeder Code, der nach var item = findItem(); muss warten, bis die Funktion das Ergebnis zurückgibt.

Asynchron

Sie rufen Ihren Freund aus demselben Grund erneut an. Aber diesmal sagen Sie ihm, dass Sie es eilig haben und er Sie über Ihr Mobiltelefon zurückrufen sollte. Sie legen auf, verlassen das Haus und tun, was Sie geplant haben. Sobald Ihr Freund Sie zurückruft, beschäftigen Sie sich mit den Informationen, die er Ihnen gegeben hat.

Genau das passiert bei einer Ajax-Anfrage.

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 werden soll, sobald die Antwort empfangen wurde, ein Rückruf (etwas Rückruf beachten). Jede nach diesem Aufruf kommende Anweisung wird ausgeführt, bevor der Rückruf aufgerufen wird.

Lösung (en)

Umfassen Sie die asynchrone Natur von JavaScript! Bestimmte asynchrone Operationen bieten zwar synchrone Gegenstücke (dies gilt auch für "Ajax"), es wird jedoch generell davon abgeraten, sie zu verwenden, insbesondere im Browser-Kontext.

Warum ist es schlecht, fragst du?

JavaScript wird im Benutzeroberflächenthread des Browsers ausgeführt. Bei einem längeren Prozess wird die Benutzeroberfläche gesperrt, so dass sie nicht mehr reagiert. Darüber hinaus gibt es eine Obergrenze für die Ausführungszeit von JavaScript, und der Browser fragt den Benutzer, ob die Ausführung fortgesetzt werden soll oder nicht.

All dies ist eine wirklich schlechte Benutzererfahrung. Der Benutzer kann nicht feststellen, ob alles einwandfrei funktioniert oder nicht. Darüber hinaus 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 +, in älteren Browsern verfügbar, wenn Sie einen Transpiler oder einen Regenerator verwenden)
  • Rückrufe (in Knoten beliebt)
  • Versprechen mit then() (ES2015 +, verfügbar in älteren Browsern, wenn Sie eine der vielen Versprechenbibliotheken verwenden)

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

ES2017 +: Versprechen mit async/await Erwarten

Mit der 2017 veröffentlichten Version von ECMAScript wurde die Unterstützung für asynchrone Funktionen auf Syntaxebene 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 / zu verstehen.

async/await baut auf Versprechungen auf: Eine async Funktion liefert immer ein Versprechen. Erwarten Sie, dass ein Versprechen "auspackt", und führen Sie entweder zu dem Wert, mit dem das Versprechen gelöst wurde, oder wirft einen Fehler aus, wenn das Versprechen abgelehnt wurde.

Wichtig: Sie können async nur innerhalb einer async Funktion verwenden. Das bedeutet, dass Sie auf oberster Ebene immer noch direkt mit dem Versprechen arbeiten müssen.

Sie können mehr über async/await lesen und auf MDN await .

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

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

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

Lassen Sie Funktionen Rückrufe annehmen

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 Callback immer dann aufgerufen, wenn der asynchrone Prozess ausgeführt wird. Normalerweise wird das Ergebnis an den Rückruf übergeben.

Im Beispiel der Frage können Sie einen Rückruf von foo annehmen und als success . 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 eine beliebige 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 wenn wir sie aufrufen, und wir geben sie einfach an den success . $.ajax also die Ajax-Anforderung erfolgreich ist, $.ajax den callback und $.ajax 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 übergeben:

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

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

ES2015 +: Versprechen mit then()

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

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

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

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

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

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

Bei unserem Ajax-Aufruf könnten wir Versprechen wie folgt 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
  });

Das Beschreiben aller Vorteile, die Versprechen bieten, sprengt den Rahmen dieser Antwort. Wenn Sie jedoch neuen Code schreiben, sollten Sie ihn ernsthaft in Betracht ziehen. Sie bieten eine hervorragende Abstraktion und Trennung Ihres Codes.

Weitere Informationen zu Versprechen: HTML5-Felsen - JavaScript-Versprechen

Randbemerkung: Die zurückgestellten Objekte von jQuery

Verzögerte Objekte sind die benutzerdefinierte Implementierung von Versprechungen von jQuery (bevor die Promise-API standardisiert wurde). Sie verhalten sich fast wie Versprechungen, weisen jedoch eine etwas andere API auf.

Jede Ajax-Methode von jQuery gibt bereits ein "verzögertes 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
});

Randbemerkung: Versprechen Sie Gotchen

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

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

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

Dieser Code versteht die oben genannten Probleme mit der Asynchronität nicht. Insbesondere $.ajax() den Code nicht ein, während er die Seite '/ password' auf Ihrem Server überprüft. Er 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 heißt, die if Anweisung holt dieses zurückgestellte Objekt immer, behandelt es als true und fährt fort, als wäre der Benutzer angemeldet. Nicht gut.

Aber das Update 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: Synchron "Ajax" -Aufrufe

Wie bereits erwähnt, haben einige (!) Asynchrone Operationen synchrone Gegenstücke. Ich plädiere nicht für deren Verwendung, aber der Vollständigkeit halber würden Sie einen synchronen Aufruf durchführen:

Ohne jQuery

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

jQuery

Wenn Sie jQuery , können Sie die Option async auf false . Beachten Sie, dass diese Option seit jQuery 1.8 veraltet ist. Sie können dann entweder weiterhin einen success 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 sie in $.ajax (da Sie nur Konfigurationsparameter an $.ajax ).

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


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

  • Behandeln Sie asynchrone HTTP-Aufrufe.
  • Warten auf Antwort von jedem API-Aufruf;
  • Verwenden Sie Promise ;
  • Verwenden Sie das Promise.All-Muster, um an mehreren HTTP-Aufrufen teilzunehmen.

Dieses Arbeitsbeispiel ist in sich abgeschlossen. Es wird ein einfaches Anforderungsobjekt definiert, das das Fensterobjekt XMLHttpRequestzum Aufruf verwendet. Es wird eine einfache Funktion definiert, die auf die Erfüllung einer Reihe von Versprechen wartet.

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

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

Für jedes Element löst eine neue Versprechung einen Block aus - ExecutionBlockparsen das Ergebnis, planen einen neuen Satz von Versprechen basierend auf dem Ergebnisarray, dh eine Liste von Spotify- userObjekten, und führen den neuen HTTP-Aufruf ExecutionProfileBlockasynchron aus.

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

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

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

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

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

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

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

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

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

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

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

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

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

    ///// promiseall again

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

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

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

    /////

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

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


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

Es gibt viele informative Antworten mit großartigen Details, ich werde keine von ihnen wiederholen. Der Schlüssel zur Programmierung in JavaScript ist zunächst das korrekte mentale Modell der Gesamtausführung.

  1. Ihre Einstiegspunkte werden als Ergebnis eines Ereignisses ausgeführt. Beispielsweise wird ein Skripttag mit Code in den Browser geladen. (Aus diesem Grund müssen Sie sich möglicherweise mit der Bereitschaft der Seite für die Ausführung Ihres Codes befassen, wenn Sie zuerst dom-Elemente erstellen müssen.)
  2. Ihr Code wird bis zum Abschluss ausgeführt - so wie viele asynchrone Aufrufe er ausführt -, ohne dass Sie Ihre Rückrufe ausführen müssen , einschließlich XHR-Anforderungen, Timeouts, dom-Ereignishandlern usw. Jeder dieser Rückrufe, die auf die Ausführung warten, wird in eine Warteschlange gestellt und wartet ihrerseits werden sie ausgeführt, nachdem alle anderen abgefeuerten Ereignisse die Ausführung beendet haben.
  3. Jeder einzelne Rückruf für eine XHR-Anforderung, ein Timeout oder ein dom, nach dem das Ereignis einmal aufgerufen wird, wird dann vollständig ausgeführt.

Die gute Nachricht ist: Wenn Sie diesen Punkt gut verstehen, müssen Sie sich nie um die Rennbedingungen kümmern. In erster Linie sollten Sie vor allem wissen, wie Sie Ihren Code als Antwort auf verschiedene diskrete Ereignisse organisieren und wie Sie diese in einer logischen Sequenz zusammenfassen möchten. Sie können Versprechungen oder ein neues async / await auf höherer Ebene als Werkzeuge zu diesem Zweck verwenden, oder Sie können Ihre eigenen Rollen ausführen.

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


Verwenden Sie eine callback()Funktion im foo()Erfolg. 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();

Anstatt Code auf Sie zu werfen, gibt es zwei Konzepte, die für das Verständnis, wie JS Callbacks und Asynchronität handhabt, ausschlaggebend sind. (Ist das überhaupt ein Wort?)

Das Ereignisschleifen- und Parallelitätsmodell

Es gibt drei Dinge, die Sie beachten sollten. Die Warteschlange; die Ereignisschleife und der Stapel

Im Großen und Ganzen ist die Ereignisschleife wie der Projektmanager. Er überwacht ständig alle Funktionen, die ausgeführt werden sollen, und kommuniziert zwischen der Warteschlange und dem Stapel.

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

Sobald eine Nachricht zum Ausführen einer Nachricht empfangen wurde, wird sie der Warteschlange hinzugefügt. Die Warteschlange ist die Liste der Dinge, die zur Ausführung anstehen (wie Ihre AJAX-Anfrage). stell es dir so vor:

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

Wenn eine dieser Nachrichten ausgeführt wird, holt sie die Nachricht aus der Warteschlange und erstellt einen Stapel. Der Stapel ist alles, was JS ausführen muss, um die Anweisung in der Nachricht auszuführen. In unserem Beispiel heißt es also, anzurufenfoobarFunc

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 geht dann zum nächsten Punkt in der Warteschlange (oder lauscht auf Nachrichten)

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

WANN wird etwas laufen

Wenn Sie mit AJAX einen Anruf an eine externe Partei tätigen oder einen asynchronen Code ausführen (z. B. setTimeout), hängt Javascript von einer Antwort ab, bevor es fortfahren kann.

Die große Frage ist, wann wird es die Antwort bekommen? Die Antwort lautet: Wir wissen es nicht - daher wartet die Ereignisschleife darauf, dass diese Nachricht sagt: "Hey, mach mich laufen". Wenn JS gerade auf diese Nachricht synchron gewartet hat, würde Ihre App einfrieren und saugen. Daher führt JS das nächste Element in der Warteschlange aus, während es darauf wartet, dass die Nachricht wieder der Warteschlange hinzugefügt wird.

Deshalb verwenden wir bei asynchroner Funktionalität sogenannte Callbacks . Es ist ein bisschen wie ein then() im wahrsten Sinne des Wortes. Wie ich verspreche, irgendetwas zurückzugeben, verwendet jQuery bestimmte Callbacks, die aufgerufen werden deffered.done deffered.failund deffered.always(unter anderem). Sie können sie alle here

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

Da ein Callback nicht sofort ausgeführt wird, sondern zu einem späteren Zeitpunkt, ist es wichtig, die Referenz an die nicht übergebene Funktion zu übergeben. so

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

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

Hoffentlich macht das Sinn. Wenn Sie auf solche Dinge stoßen, die verwirrend erscheinen, empfiehlt es sich, die Dokumentation vollständig zu lesen, um zumindest ein Verständnis davon zu erhalten. Das macht Sie zu einem viel besseren Entwickler.


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

Hier ist ein Beispiel davon:

var async = require("async");

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

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

Ich verwende das resultObjekt, um den Wert während des asynchronen Vorgangs zu speichern. Dadurch kann das Ergebnis auch nach dem asynchronen Job verfügbar sein.

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


Schauen Sie sich dieses Beispiel an:

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

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

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

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

Wie Sie sehen, getJokeist die Rückgabe eines gelösten Versprechens (wird bei der Rückgabe gelöst res.data.value). Sie warten also, bis die $ http.get- Anforderung abgeschlossen ist und anschließend console.log (res.joke) ausgeführt wird (als normaler asynchroner Fluss).

Dies ist das Programm:

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


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

In diesem Thread wurden mehrere Lösungen gegeben. Am einfachsten ist es vielleicht, ein Objekt an die foo()Methode zu übergeben und die Ergebnisse in einem Member dieses Objekts zu speichern, nachdem der async-Aufruf abgeschlossen ist.

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 Anruf an foo()nichts nützliches zurückgibt. Das Ergebnis des asynchronen Anrufs wird jetzt jedoch in gespeichert result.response.


Sie verwenden Ajax falsch. Die Idee ist, dass nichts zurückgegeben wird, sondern die Daten an eine Callback-Funktion übergeben werden, die die Daten verarbeitet.

Das ist:

function handleData( responseData ) {

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

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

Die Rückgabe von Objekten im Submit-Handler führt zu nichts. Sie müssen stattdessen entweder die Daten weitergeben oder machen, was Sie möchten, und zwar direkt in der Erfolgsfunktion.


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

Dies bedeutet AngularJS, jQuery (mit verzögertem), nativem XHR-Ersatz (Abruf), EmberJS, BackboneJS-Speichern oder einer Knotenbibliothek, die Versprechen zurückgibt.

Ihr Code sollte ungefähr so ​​aussehen:

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

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

Felix Kling hat einen guten Job geschrieben und eine Antwort für Leute geschrieben, die jQuery mit Callbacks für AJAX verwenden. Ich habe eine Antwort für native XHR. Diese Antwort gilt für die generische Verwendung von Versprechen entweder im Frontend oder im Backend.

Das Kernthema

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

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

Das heißt, wenn Sie data der von Ihnen definierte 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 zum Thema:

    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 data = 5 Teil noch nicht ausgeführt wurde. Es wird wahrscheinlich in einer Sekunde ausgeführt, ist jedoch für den zurückgegebenen Wert irrelevant.

Da die Operation noch nicht ausgeführt wurde (AJAX, Server-Aufruf, E / A-Zeitgeber), geben Sie den Wert zurück, bevor die Anforderung die Möglichkeit hatte, Ihrem Code mitzuteilen, welchen Wert dieser Wert hat.

Eine mögliche Lösung für dieses Problem ist, den Code erneut zu aktivieren und dem Programm mitzuteilen, was zu tun ist, wenn die Berechnung abgeschlossen ist. Versprechen ermöglichen dies aktiv, indem sie zeitlich (zeitempfindlich) sind.

Kurzer Rückblick auf Versprechen

Ein Versprechen ist ein Wert über die Zeit . Versprechungen haben Status, sie beginnen als anhängig ohne Wert und können sich darauf einstellen:

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

Ein Versprechen kann nur einmal den Zustand wechseln, danach bleibt es für immer im selben Zustand. Sie können then Handler an Versprechungen anhängen, um deren Wert zu ermitteln und Fehler zu behandeln. then erlauben Handler die chaining von Anrufen. Versprechen werden mithilfe von APIs erstellt, die sie zurückgeben . Zum Beispiel der modernere AJAX-Ersatzabruf oder die $.get Rückversprechen von jQuery.

Wenn wir .then ein Versprechen anrufen und etwas zurückgeben, erhalten wir ein Versprechen für den verarbeiteten Wert . Wenn wir ein anderes Versprechen zurückgeben, erhalten wir erstaunliche Dinge, aber halten wir unsere Pferde.

Mit Versprechen

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

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

Nachdem wir setTimeout nun in Versprechungen umgewandelt haben, können wir thenes zum Zählen bringen:

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

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

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

Anwenden dieses

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 zur Verarbeitung verketten. Wir wissen jetzt, wie die Antwort von einem asynchronen Aufruf zurückgegeben wird.

ES2015 (ES6)

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

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

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

Wenn die von uns produzierte Sequenz eine Folge von Aktionen ist und keine Zahlen - wir können die Funktion anhalten, wenn eine Aktion ausgeführt wird, und darauf warten, bevor wir die Funktion wieder aufnehmen. Anstelle einer Zahlenfolge brauchen wir also eine Folge zukünftiger Werte - das heißt: Versprechen.

Dieser etwas knifflige, aber sehr mächtige Trick erlaubt uns, asynchronen Code synchron zu schreiben. Es gibt mehrere "Läufer", die dies für Sie tun, wenn Sie ein paar Zeilen Code schreiben, aber den Rahmen dieser Antwort sprengen würden. Ich werde Bluebirds Promise.coroutinehier verwenden, aber es gibt andere Wrapper wie cooder Q.async.

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

Diese Methode liefert selbst ein Versprechen, das wir von anderen Coroutinen konsumieren können. Zum Beispiel:

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

ES2016 (ES7)

In ES7 ist dies weiter standardisiert, es gibt momentan mehrere Vorschläge, aber in allen kann man awaitversprechen. Dies ist nur "Zucker" (schönere Syntax) für den ES6-Vorschlag oben, indem Sie die Schlüsselwörter asyncund hinzufügen await. 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
}

Es gibt trotzdem ein Versprechen zurück :)


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

Wenn Sie jQuery nicht verwenden und ein schönes kurzes XMLHttpRequest 2 wünschen, das sowohl auf modernen Browsern als auch auf mobilen Browsern funktioniert, empfehle ich es folgendermaßen:

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 aufgeführten Funktionen.
  2. Der Rückruf wird direkt eingestellt (also keine zusätzlichen unnötigen Schließungen).
  3. Es verwendet den neuen Onload (Sie müssen also nicht den Status von readystate && überprü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 dieses Ajax-Aufrufs abzurufen (drei mit dem Variablennamen XMLHttpRequest):

Das einfachste:

this.response

Oder wenn Sie aus irgendeinem Grund den Callback an eine Klasse bind() :

e.target.response

Beispiel:

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

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

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

Nichts einfacher.

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

Testen Sie die erweiterten Funktionen von XMLHttpRequest

Es wird von allen * modernen Browsern unterstützt. Ich kann dies bestätigen, da ich diesen Ansatz verwende, da XMLHttpRequest 2 existiert. Ich hatte nie Probleme mit allen Browsern, die ich verwende.

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

Die Verwendung des Variablennamens XMLHttpRequest ist ein weiterer großer Fehler, da Sie den Callback innerhalb der Schließungen von onload / oreadystatechange ausführen müssen, andernfalls haben Sie ihn verloren.

Wenn Sie nun etwas komplexeres mit post und FormData wünschen, können Sie diese Funktion problemlos 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)
}

Nochmal ... es ist eine sehr kurze Funktion, aber es wird & postiert.

Anwendungsbeispiele:

x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data

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

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

Oder legen Sie einige benutzerdefinierte Werte fest:

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

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

Nachdem ich das gesagt habe, warum nicht einfach?

Wie im Kommentar erwähnt, zerstört die Verwendung von error && synchron den Punkt der Antwort vollständig. Welches ist ein schöner kurzer Weg, um Ajax richtig einzusetzen?

Fehlerhandler

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 Fehlerhandler, der statisch definiert ist, sodass die Funktion nicht beeinträchtigt wird. Der Fehlerhandler kann auch für andere Funktionen verwendet werden.

Um einen Fehler wirklich zu beheben, besteht der einzige Weg darin, eine falsche URL zu schreiben. In diesem Fall gibt jeder Browser einen Fehler aus.

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

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

Selbst wenn Sie 'fdggdgilfdghfldj' als formdata ü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 prüfen, ob Sie die richtigen Nachbearbeitungsdaten übergeben haben.

Domains nicht zulässig, wirft einen Fehler automatisch ab.

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 der Callback-Funktion displayAjax() .

Also: Es ist keine Fehlerprüfung erforderlich, wenn Sie die URL richtig kopieren und einfügen können. ;)

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

Lesen Sie eine Datei synchron

Tu das nicht

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

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

Jetzt können Sie tun

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

Es gibt keine andere Möglichkeit, dies nicht asynchron auszuführen. (Ja, mit setTimeout-Schleife ... aber ernsthaft?)

Ein weiterer Punkt ist ... wenn Sie mit APIs arbeiten oder einfach nur eigene Listendateien verwenden oder was immer Sie für jede Anfrage mit anderen Funktionen verwenden ...

Nur wenn Sie eine Seite haben, auf die Sie immer dasselbe 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 dienen zur Grundnutzung.

Wenn Sie die Funktion ERWEITERN wollen ...

Ja, du kannst.

Ich verwende eine Vielzahl von 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 erledigen:

Ich habe einen Download-Manager erstellt (mit Bereichen auf beiden Seiten mit Lebenslauf, Dateileiter, Dateisystem), verschiedenen Bild-Resizer-Konvertern mit Canvas, Web-Datenbanken mit Base64images und vielem mehr ... manchmal brauchen Sie einen Blob, Array-Puffer, Sie können Header setzen, den Mimetyp überschreiben und es gibt noch viel mehr ...

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


Ich werde mit einem schrecklich aussehenden, von Hand gezeichneten Comic antworten. Das zweite Bild ist der Grund, warum resultes undefinedin Ihrem Codebeispiel ist.


Die Frage war:

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

was kann interpretiert werden als:

Wie kann asynchroner Code synchron aussehen ?

Die Lösung besteht darin, Rückrufe zu vermeiden und eine Kombination aus Versprechungen und Asynchron / Erwartung zu verwenden .

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

(Obwohl es in Javascript geschrieben werden kann, schreibe ich es lieber in Python und kompiliere es mit Transcrypt zu Javascript . Es wird klar genug sein.)

Aktivieren Sie zunächst die JQuery-Verwendung, um Folgendes zur $Verfügung zu haben S:

__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 so, als wäre er synchron :

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

Js ist ein einzelner Thread.

Der Browser kann in drei Teile unterteilt werden:

1) Ereignisschleife

2) Web-API

3) Ereigniswarteschlange

Event-Loop wird für immer ausgeführt, dh eine Art Endlos-Loop. Event-Warteschlange ist die Position, in der alle Ihre Funktion auf ein Event gedrückt wird (Beispiel: click). Dies wird einer nach dem anderen aus der Warteschlange ausgeführt und in eine Event-Schleife eingefügt, die diese Funktion ausführt und sich selbst vorbereitet for next one after first wird ausgeführt. Dies bedeutet, dass die Ausführung einer Funktion erst gestartet wird, wenn die Funktion ausgeführt wird, bevor sie in der Warteschlange in der Ereignisschleife ausgeführt wird.

Nehmen wir nun an, wir hätten zwei Funktionen in eine Warteschlange gestellt, eine für das 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 useiseData (). Die serverRequest-Funktion geht in eine Ereignisschleife und ruft den Server an, da wir nie wissen, wie viel Zeit erforderlich ist, um Daten vom Server abzurufen. Es wird also erwartet, dass dieser Prozess Zeit in Anspruch nimmt. Daher beschäftigen wir uns mit unserer Ereignisschleife und hängen unsere Seite auf Die API kommt in die Rolle, dass sie diese Funktion aus der Ereignisschleife übernimmt und befasst sich mit dem Server, der die Ereignisschleife frei macht, so dass wir die nächste Funktion aus der Warteschlange ausführen können. Die nächste Funktion in der Warteschlange ist utiliseData (), die in einer Schleife abläuft, aber aufgrund fehlender Daten nicht Verschwendung und Ausführung der nächsten Funktion wird bis zum Ende der Warteschlange fortgesetzt (dies wird als asynchrones Aufrufen bezeichnet, dh wir können etwas anderes tun, bis wir Daten erhalten).

Nehmen wir an, unsere serverRequest () - Funktion hatte eine return-Anweisung in einem Code. Wenn wir Daten vom Server zurückerhalten, wird die Web-API diese am Ende der Warteschlange in die Warteschlange stellen. Da es am Ende der Warteschlange gedrückt wird, können wir seine Daten nicht verwenden, da in der Warteschlange keine Funktion zur Verwendung dieser Daten vorhanden ist. Daher ist es nicht möglich, etwas von Async Call zurückzugeben.

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

Ein Bild aus einer der Antworten hier, erläutert die Verwendung von Rückrufen auf korrekte Weise ... Wir geben unsere Funktion (Funktion, die vom Server zurückgegebene Daten verwendet) an den aufrufenden Server weiter.

 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 den asynchronen Anruf (@Felix Kling Answer on Top) https://.com/a/14220323/7579856





ecmascript-2017