Wrangle Async-Aufgaben mit JQuery-Versprechen

Versprechungen sind eine aufregende jQuery-Funktion, die das Verwalten von asynchronen Ereignissen zum Kinderspiel macht. Sie ermöglichen es Ihnen, klarere, kürzere Rückrufe zu schreiben und die Anwendungslogik auf hoher Ebene von den Verhaltensweisen auf niedriger Ebene zu trennen.

Wenn Sie die Versprechungen verstanden haben, sollten Sie sie für alles von AJAX-Aufrufen bis zum UI-Fluss verwenden. Das ist ein Versprechen!


Versprechen verstehen

Sobald ein Versprechen gelöst oder abgelehnt wird, bleibt es für immer in diesem Zustand.

Ein Promise ist ein Objekt, das ein einmaliges Ereignis darstellt, normalerweise das Ergebnis einer asynchronen Aufgabe wie eines AJAX-Aufrufs. Zunächst ist ein Versprechen in einer steht aus Zustand. Schließlich ist es entweder aufgelöst (dh die Aufgabe ist erledigt) oder abgelehnt (falls die Aufgabe fehlgeschlagen ist). Sobald ein Versprechen gelöst oder abgelehnt wird, bleibt es für immer in diesem Zustand und seine Rückrufe werden nie wieder ausgelöst.

Sie können dem Versprechen Rückrufe hinzufügen, die ausgelöst werden, wenn das Versprechen gelöst oder abgelehnt wird. Und Sie können jederzeit weitere Rückrufe hinzufügen - auch nachdem das Versprechen gelöst / abgelehnt wurde! (In diesem Fall werden sie sofort schießen.)

Außerdem können Sie Versprechen logisch zu neuen Versprechen kombinieren. Das macht es leicht, Code zu schreiben, der besagt: "Wenn all diese Dinge geschehen sind, tun Sie diese andere Sache."

Und das ist alles, was Sie über Versprechungen abstrakt wissen müssen. Es gibt mehrere JavaScript-Implementierungen zur Auswahl. Die zwei wichtigsten sind Kris Kowals q, basierend auf den CommonJS Promises / A-Angaben, und jQuery Promises (hinzugefügt in jQuery 1.5). Aufgrund der Allgegenwart von jQuery verwenden wir die Implementierung in diesem Tutorial.


Versprechen machen mit $ .Deferred

Jedes jQuery-Promise beginnt mit einem Deferred. Ein Aufgeschobenes ist nur ein Versprechen mit Methoden, mit denen der Besitzer es auflösen oder ablehnen kann. Bei allen anderen Versprechungen handelt es sich um "Nur-Lese" -Kopien eines Zurückgestellten. Wir werden im nächsten Abschnitt darüber sprechen. Um ein Deferred zu erstellen, verwenden Sie die $ .Deferred () Konstrukteur:

Ein Aufgeschobenes ist nur ein Versprechen mit Methoden, mit denen der Besitzer es auflösen oder ablehnen kann.

 var aufgeschoben = new $ .Deferred (); deferred.state (); // "pending" deferred.resolve (); deferred.state (); // "gelöst" deferred.reject (); // keine Wirkung, weil das Versprechen bereits gelöst wurde

(Versionshinweis: Zustand() wurde in jQuery 1.7 hinzugefügt. In 1.5 / 1.6 verwenden ist abgelehnt() und ist gelöst().)

Wir können ein "reines" Versprechen erhalten, indem wir ein Deferred's rufen versprechen() Methode. Das Ergebnis ist identisch mit dem Zurückgestellt, außer dass Entschlossenheit() und ablehnen() Methoden fehlen.

 var aufgeschoben = new $ .Deferred (); var promise = deferred.promise (); Versprechen.Zustand (); // "pending" deferred.reject (); Versprechen.Zustand (); // "abgelehnt"

Das versprechen() Die Methode existiert nur für die Kapselung: Wenn Sie ein Deferred aus einer Funktion zurückgeben, wird es möglicherweise vom Aufrufer aufgelöst oder zurückgewiesen. Wenn Sie jedoch nur das reine Versprechen zurückgeben, das diesem Zurückgestellt entspricht, kann der Anrufer nur seinen Status lesen und Callbacks anhängen. jQuery selbst verfolgt diesen Ansatz und gibt pure Promises aus seinen AJAX-Methoden zurück:

 var gettingProducts = $ .get ("/ products"); gettingProducts.state (); // "ausstehend" gettingProducts.resolve; // nicht definiert

Verwendung der -ing Im Namen einer Versprechung wird deutlich, dass es sich um einen Prozess handelt.


Modellieren eines UI-Flusses mit Versprechen

Nachdem Sie ein Versprechen erhalten haben, können Sie mit dem Telefon beliebig viele Rückrufe anhängen erledigt(), Scheitern(), und immer() Methoden:

 promise.done (function () console.log ("Wird ausgeführt, wenn dieses Versprechen gelöst wird.");); promise.fail (function () console.log ("Wird ausgeführt, wenn dieses Promise abgelehnt wird.");); promise.always (function () console.log ("Und das wird so oder so laufen."););

Versionshinweis: immer() wurde als bezeichnet Komplett() vor jQuery 1.6.

Es gibt auch eine Abkürzung, um alle diese Arten von Rückrufen gleichzeitig anzufügen, dann():

 promise.then (doneCallback, failCallback, alwaysCallback);

Rückrufe werden garantiert in der Reihenfolge ausgeführt, in der sie angefügt wurden.

Ein großer Anwendungsfall für Versprechungen ist die Darstellung einer Reihe von möglichen Aktionen des Benutzers. Nehmen wir zum Beispiel ein einfaches AJAX-Formular. Wir möchten sicherstellen, dass das Formular nur einmal gesendet werden kann und dass der Benutzer beim Senden des Formulars eine Bestätigung erhält. Außerdem möchten wir, dass der Code, der das Verhalten der Anwendung beschreibt, vom Code getrennt wird, der die Markierung der Seite berührt. Dies erleichtert das Testen von Einheiten und minimiert die Menge an Code, die geändert werden muss, wenn wir unser Seitenlayout ändern.

 // Anwendungslogik var sendendeFeedback = new $ .Deferred (); submittedFeedback.done (Funktion (Eingabe) $ .post ("/ feedback", Eingabe);); // DOM-Interaktion $ ("# feedback"). Submit (function () submittedFeedback.resolve ($ ("textarea", this) .val ()); false zurückgeben; // Standardverhalten von Formularen verhindern); submittedFeedback.done (function () $ ("# container"). append (")

Danke für Ihre Rückmeldung!

"););

(Wir nutzen die Tatsache aus, dass die Argumente auf weitergegeben wurden Entschlossenheit()/ablehnen() werden wörtlich an jeden Rückruf weitergeleitet.)


Versprechen aus der Zukunft leihen

Rohr() gibt ein neues Versprechen zurück, das alle von einem der Versionsanfragen zurückgegebenen Versprechen nachahmt Rohr() Rückrufe.

Unser Feedback-Formularcode sieht gut aus, aber die Interaktion kann verbessert werden. Anstatt optimistisch anzunehmen, dass unser POST-Aufruf erfolgreich sein wird, sollten wir zuerst angeben, dass das Formular gesendet wurde (z. B. mit einem AJAX-Spinner), und dann dem Benutzer mitteilen, ob die Übermittlung erfolgreich war oder fehlgeschlagen ist, wenn der Server antwortet.

Wir können dies tun, indem wir Rückrufe an das von zurückgegebene Promise anhängen $ .post. Darin liegt jedoch eine Herausforderung: Wir müssen das DOM anhand dieser Rückrufe manipulieren, und wir haben geschworen, unseren DOM-berührenden Code aus unserem Anwendungslogikcode herauszuhalten. Wie können wir das tun, wenn das POST Promise in einem Callback der Anwendungslogik erstellt wird?

Eine Lösung besteht darin, die Auflösungs- / Ablehnungsereignisse aus dem POST-Versprechen an ein Versprechen "weiterzuleiten", das im äußeren Bereich lebt. Aber wie machen wir das ohne mehrere fade Boilerplates (promise1.done (promise2.resolve);…)? Zum Glück bietet jQuery genau zu diesem Zweck eine Methode: Rohr().

Rohr() hat die gleiche Schnittstelle wie dann() (erledigt() Ruf zurück, ablehnen() Ruf zurück, immer() Ruf zurück; Jeder Rückruf ist optional), jedoch mit einem entscheidenden Unterschied: Während dann() gibt einfach das Versprechen zurück, mit dem es verbunden ist (zum Verketten), Rohr() gibt ein neues Versprechen zurück, das alle von einem der Versionsanfragen zurückgegebenen Versprechen nachahmt Rohr() Rückrufe. Zusamenfassend, Rohr() ist ein Fenster in die Zukunft, mit dem wir Verhalten an ein Versprechen binden können, das noch nicht einmal existiert.

Hier ist unser neu und verbessert Formularcode, wobei unser POST-Promise zu einem Promise-Pipe geleitet wird saveFeedback:

 // Anwendungslogik var sendendeFeedback = new $ .Deferred (); var savingFeedback = submissionsFeedback.pipe (Funktion (Eingabe) return $ .post ("/ feedback", Eingabe);); // DOM-Interaktion $ ("# feedback"). Submit (function () submittedFeedback.resolve ($ ("textarea", this) .val ()); return false; // Standardverhalten von Formularen verhindern); submittedFeedback.done (function () $ ("# container"). append (")
");); saveFeedback.then (function () $ (" # container "). anhängen ("

Danke für Ihre Rückmeldung!

");, function () $ (" # container "). anhängen ("

Bei der Verbindung zum Server ist ein Fehler aufgetreten.

");, function () $ (" # container "). remove (". spinner "););

Die Kreuzung der Versprechen finden

Ein Teil des Genies der Verheißungen ist ihre binäre Natur. Da sie nur zwei mögliche Zustände haben, können sie wie Booleans kombiniert werden (obwohl Booleans, deren Werte möglicherweise noch nicht bekannt sind)..

Das Versprechen entspricht der logischen Kreuzung (UND) ist gegeben durch $ .when (). Eine Liste von Versprechen gegeben, wann() gibt ein neues Versprechen zurück, das diesen Regeln folgt:

  1. Wann alles von den gegebenen Versprechen gelöst, wird das neue Versprechen gelöst.
  2. Wann irgendein von den gegebenen Versprechen wird abgelehnt, wird das neue Versprechen abgelehnt.

Jedes Mal, wenn Sie auf das Auftreten mehrerer ungeordneter Ereignisse warten, sollten Sie die Verwendung in Betracht ziehen wann().

Gleichzeitige AJAX-Aufrufe sind ein naheliegender Anwendungsfall:

 $ ("# container"). anhängen ("
"); $ .when ($. get (" / encryptedData "), $ .get (" / encryptKey ")). Dann (function () // beide AJAX-Aufrufe waren erfolgreich), function () // one der AJAX-Aufrufe ist fehlgeschlagen, function () $ ("# container"). remove (". spinner"););

Ein anderer Anwendungsfall ermöglicht es dem Benutzer, eine Ressource anzufordern, die möglicherweise bereits verfügbar ist oder nicht. Angenommen, wir haben ein Chat-Widget, das wir mit YepNope laden (siehe Easy Script Loading mit yepnope.js).

 var loadingChat = new $ .Deferred (); yepnope (load: "resources / chat.js", komplett: loadingChat.resolve); var launchingChat = new $ .Deferred (); $ ("# launchChat"). click (launchingChat.resolve); launchingChat.done (function () $ ("# chatContainer"). anhängen ("
");); $ .when (loadingChat, launchingChat) .done (function () $ (" # chatContainer "). remove (". spinner "); // Chat starten);

Fazit

Versprechen haben sich als unverzichtbares Instrument im Kampf gegen asynchronen Spaghetti-Code erwiesen. Durch die binäre Repräsentation einzelner Aufgaben wird die Anwendungslogik klarer und die Zustandsverfolgung wird reduziert.

Wenn Sie mehr über Versprechen und andere Tools zum Schutz Ihrer Gesundheit in einer immer asynchroner werdenden Welt erfahren möchten, lesen Sie mein nächstes eBook: Async JavaScript: Rezepte für ereignisgesteuerten Code (voraussichtlich im März)..