JavaScript Promises: Ein Tutorial mit Beispielen

Veröffentlicht: 2022-03-11

Promises sind ein heißes Thema in JavaScript-Entwicklerkreisen, und Sie sollten sich unbedingt damit vertraut machen. Sie sind nicht leicht zu verstehen; Es kann ein paar Tutorials, Beispiele und eine anständige Menge an Übung erfordern, um sie zu verstehen.

Mein Ziel mit diesem Tutorial ist es, Ihnen zu helfen, JavaScript Promises zu verstehen und Sie dazu anzuregen, sie mehr zu üben. Ich werde erklären, was Versprechen sind, welche Probleme sie lösen und wie sie funktionieren. Jeder in diesem Artikel beschriebene Schritt wird von einem jsbin -Codebeispiel begleitet, das Ihnen bei der Arbeit hilft und als Grundlage für weitere Erkundungen dient.

JavaScript-Promises werden in diesem umfassenden Tutorial erklärt.

Was ist ein JavaScript-Versprechen?

Ein Versprechen ist eine Methode, die letztendlich einen Wert produziert. Sie kann als das asynchrone Gegenstück einer Getter-Funktion betrachtet werden. Seine Essenz kann wie folgt erklärt werden:

 promise.then(function(value) { // Do something with the 'value' });

Promises können die asynchrone Verwendung von Rückrufen ersetzen und bieten ihnen gegenüber mehrere Vorteile. Sie beginnen an Boden zu gewinnen, da immer mehr Bibliotheken und Frameworks sie als ihre primäre Methode zum Umgang mit Asynchronität nutzen. Ember.js ist ein großartiges Beispiel für ein solches Framework.

Es gibt mehrere Bibliotheken, die die Promises/A+-Spezifikation implementieren. Wir lernen das Grundvokabular und arbeiten einige JavaScript Promise-Beispiele durch, um die Konzepte dahinter praktisch vorzustellen. In den Codebeispielen verwende ich eine der bekannteren Implementierungsbibliotheken, rsvp.js.

Machen Sie sich bereit, wir werden viele Würfel werfen!

Abrufen der rsvp.js-Bibliothek

Promises und damit rsvp.js können sowohl server- als auch clientseitig verwendet werden. Um es für nodejs zu installieren, gehen Sie zu Ihrem Projektordner und geben Sie Folgendes ein:

 npm install --save rsvp

Wenn Sie am Front-End arbeiten und Bower verwenden, ist es nur ein

 bower install -S rsvp

Weg.

Wenn Sie einfach nur direkt ins Spiel einsteigen möchten, können Sie es über ein einfaches Skript-Tag einbinden (und mit jsbin können Sie es über das Dropdown-Menü „Bibliothek hinzufügen“ hinzufügen):

 <script src="//cdn.jsdelivr.net/rsvp/3.0.6/rsvp.js"></script>

Welche Eigenschaften hat ein Versprechen?

Ein Versprechen kann einen von drei Zuständen haben: ausstehend , erfüllt oder abgelehnt . Nach der Erstellung befindet sich das Promise im Status „Ausstehend“. Von hier aus kann es entweder in den Status „Erfüllt“ oder „Abgelehnt“ wechseln. Wir nennen diesen Übergang die Auflösung der Verheißung . Der aufgelöste Zustand eines Versprechens ist sein endgültiger Zustand. Sobald es also erfüllt oder abgelehnt wurde, bleibt es dort.

Der Weg, ein Promise in rsvp.js zu erstellen, führt über einen sogenannten offenbarenden Konstruktor. Diese Art von Konstruktor nimmt einen einzelnen Funktionsparameter und ruft ihn sofort mit zwei Argumenten auf, „ fulfill “ und „ reject “, die das Promise entweder in den Status „ fulfilled oder „ rejected “ überführen können:

 var promise = new RSVP.Promise(function(fulfill, reject) { (...) });

Dieses JavaScript-Versprechensmuster wird als aufdeckender Konstruktor bezeichnet, da das einzelne Funktionsargument seine Fähigkeiten der Konstruktorfunktion offenbart, aber sicherstellt, dass die Verbraucher des Versprechens seinen Zustand nicht manipulieren können.

Konsumenten des Promise können auf seine Zustandsänderungen reagieren, indem sie ihren Handler über die then -Methode hinzufügen. Es benötigt eine Erfüllungs- und eine Ablehnungs-Handler-Funktion, die beide fehlen können.

 promise.then(onFulfilled, onRejected);

Abhängig vom Ergebnis des Auflösungsprozesses des Versprechens wird entweder der onFulfilled oder der onRejected Handler asynchron aufgerufen.

Sehen wir uns ein Beispiel an, das zeigt, in welcher Reihenfolge die Dinge ausgeführt werden:

 function dieToss() { return Math.floor(Math.random() * 6) + 1; } console.log('1'); var promise = new RSVP.Promise(function(fulfill, reject) { var n = dieToss(); if (n === 6) { fulfill(n); } else { reject(n); } console.log('2'); }); promise.then(function(toss) { console.log('Yay, threw a ' + toss + '.'); }, function(toss) { console.log('Oh, noes, threw a ' + toss + '.'); }); console.log('3');

Dieses Snippet druckt eine Ausgabe ähnlich der folgenden:

 1 2 3 Oh, noes, threw a 4.

Oder, wenn wir Glück haben, sehen wir:

 1 2 3 Yay, threw a 6.

Dieses Versprechen-Tutorial zeigt zwei Dinge.

Erstens, dass die Handler, die wir an das Promise angehängt haben, tatsächlich aufgerufen wurden, nachdem der gesamte andere Code asynchron ausgeführt wurde.

Zweitens, dass der Fulfillment-Handler nur aufgerufen wurde, wenn das Versprechen erfüllt wurde, mit dem Wert, mit dem es aufgelöst wurde (in unserem Fall das Ergebnis des Würfelwurfs). Dasselbe gilt für den Rejection Handler.

Verkettung von Versprechungen und Rinnsal

Die Spezifikation erfordert, dass die then -Funktion (die Handler) auch ein Promise zurückgeben muss, was das Verketten von Promises ermöglicht, was zu einem Code führt, der fast synchron aussieht:

 signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage)

Hier gibt signupPayingUser ein Promise zurück, und jede Funktion in der Promise-Kette wird mit dem Rückgabewert des vorherigen Handlers aufgerufen, sobald sie abgeschlossen ist. Praktischerweise werden die Aufrufe dadurch serialisiert, ohne den Hauptausführungsthread zu blockieren.

Um zu sehen, wie jedes Versprechen mit dem Rückgabewert des vorherigen Elements in der Kette aufgelöst wird, kehren wir zum Würfeln zurück. Wir wollen maximal dreimal würfeln, bzw. bis die ersten sechs kommen jsbin:

 function dieToss() { return Math.floor(Math.random() * 6) + 1; } function tossASix() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; if (n === 6) { fulfill(n); } else { reject(n); } }); } function logAndTossAgain(toss) { console.log("Tossed a " + toss + ", need to try again."); return tossASix(); } function logSuccess(toss) { console.log("Yay, managed to toss a " + toss + "."); } function logFailure(toss) { console.log("Tossed a " + toss + ". Too bad, couldn't roll a six"); } tossASix() .then(null, logAndTossAgain) //Roll first time .then(null, logAndTossAgain) //Roll second time .then(logSuccess, logFailure); //Roll third and last time

Wenn Sie diesen Promises-Beispielcode ausführen, sehen Sie in der Konsole etwa Folgendes:

 Tossed a 2, need to try again. Tossed a 1, need to try again. Tossed a 4. Too bad, couldn't roll a six.

Das von tossASix zurückgegebene Promise wird zurückgewiesen, wenn der Wurf keine Sechs ist, also wird der Rejection Handler mit dem eigentlichen Wurf aufgerufen. logAndTossAgain dieses Ergebnis auf der Konsole aus und gibt ein Versprechen zurück, das einen weiteren Würfelwurf darstellt. Dieser Wurf wird wiederum vom nächsten logAndTossAgain abgelehnt und abgemeldet.

Manchmal hat man jedoch Glück* und schafft es, eine Sechs zu würfeln:

 Tossed a 4, need to try again. Yay, managed to toss a 6.

* Sie müssen nicht so viel Glück haben. Es besteht eine Chance von ~42 %, mindestens eine Sechs zu würfeln, wenn Sie drei Würfel würfeln.

Dieses Beispiel lehrt uns auch etwas mehr. Sehen Sie, wie nach dem ersten erfolgreichen Wurf einer Sechs keine Würfe mehr gemacht wurden? Beachten Sie, dass alle Fulfillment-Handler (die ersten Argumente in den Aufrufen von then ) in der Kette null sind, mit Ausnahme des letzten logSuccess . Die Spezifikation erfordert, dass, wenn ein Handler (Erfüllung oder Ablehnung) keine Funktion ist, das zurückgegebene Promise mit demselben Wert aufgelöst (erfüllt oder abgelehnt) werden muss. Im obigen Promise-Beispiel ist der Fulfillment-Handler, null , keine Funktion und der Wert des Promise wurde mit 6 erfüllt. Das Promise, das vom then -Aufruf (dem nächsten in der Kette) zurückgegeben wird, wird also ebenfalls erfüllt mit 6 als Wert.

Dies wiederholt sich, bis ein tatsächlicher Fulfillment-Handler (einer, der eine Funktion ist) vorhanden ist, sodass das Fulfillment nach unten sickert, bis es bearbeitet wird. In unserem Fall geschieht dies am Ende der Kette, wo es fröhlich auf der Konsole abgemeldet wird.

Umgang mit Fehlern

Die Promises/A+-Spezifikation verlangt, dass, wenn ein Promise abgelehnt wird oder ein Fehler in einen Rejection-Handler geworfen wird, dies von einem Rejection-Handler behandelt werden sollte, der der Quelle „nachgeschaltet“ ist.

Die Nutzung der folgenden Trickle-Down-Technik bietet eine saubere Möglichkeit, mit Fehlern umzugehen:

 signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage) .then(null, displayAndSendErrorReport)

Da ein Ablehnungshandler nur ganz am Ende der Kette hinzugefügt wird, sickert er nach unten, wenn ein Ausführungshandler in der Kette abgelehnt wird oder einen Fehler ausgibt, bis er auf displayAndSendErrorReport .

Kehren wir zu unseren geliebten Würfeln zurück und sehen uns das in Aktion an. Angenommen, wir wollen nur asynchron würfeln und die Ergebnisse ausdrucken:

 var tossTable = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six' }; function toss() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; fulfill(n); }); } function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUppercase() + "."); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain);

Wenn Sie dies ausführen, passiert nichts. Nichts wird auf der Konsole gedruckt und anscheinend werden keine Fehler ausgegeben.

In Wirklichkeit wird ein Fehler ausgegeben, wir sehen ihn nur nicht, da es keine Ablehnungs-Handler in der Kette gibt. Da der Code in den Handlern asynchron mit einem frischen Stack ausgeführt wird, wird er nicht einmal bei der Konsole abgemeldet. Lassen Sie uns das beheben:

 function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUpperCase() + "."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain) .then(null, logErrorMessage);

Beim Ausführen des obigen Codes wird der Fehler jetzt angezeigt:

 "Tossed a TWO." "Oops: Cannot read property 'toUpperCase' of undefined"

Wir haben vergessen, etwas von logAndTossAgain , und das zweite Versprechen wird mit undefined erfüllt. Der nächste Fulfillment-Handler explodiert dann und versucht, dabei toUpperCase . Das ist eine weitere wichtige Sache, an die Sie sich erinnern sollten: Geben Sie immer etwas von den Handlern zurück oder seien Sie darauf vorbereitet, dass in nachfolgenden Handlern nichts übergeben wird.

Höher bauen

Wir haben jetzt die Grundlagen von JavaScript-Promises im Beispielcode dieses Tutorials gesehen. Ein großer Vorteil ihrer Verwendung besteht darin, dass sie auf einfache Weise zusammengestellt werden können, um „zusammengesetzte“ Versprechen mit dem von uns gewünschten Verhalten zu erstellen. Die rsvp.js Bibliothek bietet eine Handvoll davon, und Sie können jederzeit Ihre eigenen erstellen, indem Sie die Primitiven und diese übergeordneten verwenden.

Für das letzte, komplexeste Beispiel reisen wir in die Welt des AD&D-Rollenspiels und würfeln, um Charakterwerte zu erhalten. Solche Punktzahlen werden durch das Werfen von drei Würfeln für jede Fähigkeit des Charakters erzielt.

Lassen Sie mich zuerst den Code hier einfügen und dann erklären, was neu ist:

 function toss() { var n = Math.floor(Math.random() * 6) + 1; return new RSVP.resolve(n); // [1] } function threeDice() { var tosses = []; function add(x, y) { return x + y; } for (var i=0; i<3; i++) { tosses.push(toss()); } return RSVP.all(tosses).then(function(results) { // [2] return results.reduce(add); // [3] }); } function logResults(result) { console.log("Rolled " + result + " with three dice."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } threeDice() .then(logResults) .then(null, logErrorMessage);

Toss kennen toss aus dem letzten Codebeispiel. Es schafft einfach ein Versprechen, das immer mit dem Ergebnis eines Würfelwurfs erfüllt wird. Ich habe RSVP.resolve verwendet, eine bequeme Methode, die ein solches Versprechen mit weniger Aufwand erstellt (siehe [1] im obigen Code).

In threeDice habe ich 3 Promises erstellt, die jeweils einen Würfelwurf darstellen und diese schließlich mit RSVP.all kombiniert. RSVP.all nimmt eine Reihe von Versprechen und wird mit einer Reihe ihrer aufgelösten Werte aufgelöst, einer für jedes konstituierende Versprechen, während ihre Reihenfolge beibehalten wird. Das heißt, wir haben das Ergebnis der Würfe in results (siehe [2] im obigen Code) und wir geben ein Versprechen zurück, das mit ihrer Summe erfüllt wird (siehe [3] im obigen Code).

Beim Auflösen des resultierenden Versprechens wird dann die Gesamtzahl protokolliert:

 "Rolled 11 with three dice"

Mit Versprechen echte Probleme lösen

JavaScript -Versprechen werden verwendet, um Probleme in Anwendungen zu lösen, die weitaus komplexer sind als asynchrone Würfelwürfe ohne triftigen Grund .

Wenn Sie das Rollen von drei Würfeln durch das Senden von drei Ajax-Anforderungen an separate Endpunkte ersetzen und fortfahren, wenn alle erfolgreich zurückgegeben wurden (oder wenn einer von ihnen fehlgeschlagen ist), haben Sie bereits eine nützliche Anwendung von Promises und RSVP.all .

Promises erzeugen, wenn sie richtig verwendet werden, leicht lesbaren Code, der einfacher zu begründen ist und daher einfacher zu debuggen ist als Callbacks. Es besteht keine Notwendigkeit, Konventionen beispielsweise bezüglich der Fehlerbehandlung aufzustellen, da diese bereits Teil der Spezifikation sind.

Wir haben in diesem JavaScript-Tutorial kaum an der Oberfläche dessen gekratzt, was Promises bewirken können. Promise-Bibliotheken bieten ein gutes Dutzend Methoden und Low-Level-Konstruktoren, die Ihnen zur Verfügung stehen. Meistern Sie diese, und der Himmel ist die Grenze dessen, was Sie damit machen können.

Über den Autor

Balint Erdi war vor langer Zeit ein großer Rollenspiel- und AD&D-Fan und ist jetzt ein großes Versprechen und Ember.js-Fan. Was konstant geblieben ist, ist seine Leidenschaft für Rock & Roll. Deshalb beschloss er, ein Buch über Ember.js zu schreiben, das Rock & Roll als Thema der Anwendung im Buch verwendet. Melden Sie sich hier an, um zu erfahren, wann es startet.