So schreiben Sie ein JavaScript-Versprechen

Was ist ein Versprechen?

Ein JavaScript-Versprechen ist ein Objekt, das die Fertigstellung oder den Ausfall einer asynchronen Aufgabe und den daraus resultierenden Wert darstellt.¹

Das Ende.

Ich mache natürlich Witze. Was bedeutet diese Definition überhaupt?

Erstens sind viele Dinge in JavaScript Objekte. Sie können ein Objekt auf verschiedene Arten erstellen. Der gebräuchlichste Weg ist die Objektliteral-Syntax:

const myCar = { color: 'blue', type: 'sedan', doors: '4', };

Sie können auch ein erstellen classund es mit dem newSchlüsselwort instanziieren .

class Car { constructor(color, type, doors) { this.color = color; this.type = type; this.doors = doors } } const myCar = new Car('blue', 'sedan', '4');

console.log(myCar);

Ein Versprechen ist einfach ein Objekt, das wir wie das spätere Beispiel erstellen. Wir instanziieren es mit dem newSchlüsselwort. Anstelle der drei Parameter, die wir für unser Auto eingegeben haben (Farbe, Typ und Türen), übergeben wir eine Funktion, die zwei Argumente akzeptiert: resolveund reject.

Letztendlich sagen uns Versprechen etwas über den Abschluss der asynchronen Funktion, von der wir sie zurückgegeben haben - ob sie funktioniert hat oder nicht. Wir sagen, dass die Funktion erfolgreich war, indem wir sagten, dass das Versprechen gelöst wurde , und erfolglos, indem wir sagten , dass das Versprechen abgelehnt wurde.

const myPromise = new Promise(function(resolve, reject) {});

console.log(myPromise);

const myPromise = new Promise(function(resolve, reject) { resolve(10); });

Sehen Sie, nicht zu beängstigend - nur ein Objekt, das wir erstellt haben. Und wenn wir es ein bisschen erweitern:

Darüber hinaus können wir alles, was wir möchten, in Entschlossenheit und Ablehnung überführen. Zum Beispiel könnten wir ein Objekt anstelle einer Zeichenfolge übergeben:

return new Promise((resolve, reject) => { if(somethingSuccesfulHappened) { const successObject = { msg: 'Success', data,//...some data we got back } resolve(successObject); } else { const errorObject = { msg: 'An error occured', error, //...some error we got back } reject(errorObject); } });

Oder, wie wir zuvor gesehen haben, müssen wir nichts passieren:

return new Promise((resolve, reject) => { if(somethingSuccesfulHappend) { resolve() } else { reject(); } });

Was ist mit dem "asynchronen" Teil der Definition?

JavaScript ist Single-Threaded. Dies bedeutet, dass jeweils nur eine Sache ausgeführt werden kann. Wenn Sie sich eine Straße vorstellen können, können Sie sich JavaScript als einspurige Autobahn vorstellen. Bestimmter Code (asynchroner Code) kann auf die Schulter verschoben werden, damit anderer Code ihn weitergeben kann. Wenn dieser asynchrone Code fertig ist, kehrt er zur Fahrbahn zurück.

Als Randnotiz können wir ein Versprechen von jeder Funktion zurückgeben. Es muss nicht asynchron sein. Allerdings werden Versprechen normalerweise in Fällen zurückgegeben, in denen die Funktion, von der sie zurückkehren, asynchron ist. Zum Beispiel wäre eine API mit Methoden zum Speichern von Daten auf einem Server ein guter Kandidat, um ein Versprechen zurückzugeben!

Das wegnehmen:

Mit Versprechen können wir warten, bis unser asynchroner Code vollständig ist, einige Werte daraus erfassen und diese Werte an andere Teile unseres Programms weitergeben.

Ich habe hier einen Artikel, der sich eingehender mit diesen Konzepten befasst: Für eine Schleife geworfen: Grundlegendes zu Schleifen und Zeitüberschreitungen in JavaScript.

Wie setzen wir ein Versprechen ein?

Das Verwenden eines Versprechens wird auch als Konsumieren eines Versprechens bezeichnet. In unserem obigen Beispiel gibt unsere Funktion ein Versprechungsobjekt zurück. Dies ermöglicht es uns, die Methodenverkettung mit unserer Funktion zu verwenden.

Hier ist ein Beispiel für die Verkettung von Methoden, die Sie bestimmt gesehen haben:

const a = 'Some awesome string'; const b = a.toUpperCase().replace('ST', '').toLowerCase(); console.log(b); // some awesome ring

Erinnern Sie sich jetzt an unser (vorgetäuschtes) Versprechen:

const somethingWasSuccesful = true; function someAsynFunction() { return new Promise((resolve, reject){ if (somethingWasSuccesful) { resolve(); } else { reject() } }); }

Und unser Versprechen durch Methodenverkettung verbrauchen:

someAsyncFunction .then(runAFunctionIfItResolved(withTheResolvedValue)) .catch(orARunAfunctionIfItRejected(withTheRejectedValue));

Ein (mehr) reales Beispiel.

Stellen Sie sich vor, Sie haben eine Funktion, mit der Benutzer aus einer Datenbank abgerufen werden. Ich habe eine Beispielfunktion für Codepen geschrieben, die eine API simuliert, die Sie möglicherweise verwenden. Es bietet zwei Optionen für den Zugriff auf die Ergebnisse. Zum einen können Sie eine Rückruffunktion bereitstellen, über die Sie auf den Benutzer oder einen Fehler zugreifen können. Oder zweitens gibt die Funktion ein Versprechen zurück, um auf den Benutzer oder Fehler zuzugreifen.

Traditionell würden wir über Rückrufe auf die Ergebnisse von asynchronem Code zugreifen.

rr someDatabaseThing(maybeAnID, function(err, result)) { //...Once we get back the thing from the database... if(err) { doSomethingWithTheError(error) } else { doSomethingWithResults(results); } }

Die Verwendung von Rückrufen ist in Ordnung, bis sie übermäßig verschachtelt sind. Mit anderen Worten, Sie müssen mit jedem neuen Ergebnis mehr asynchronen Code ausführen. Dieses Muster von Rückrufen innerhalb von Rückrufen kann zu etwas führen, das als "Rückrufhölle" bekannt ist.

Versprechen bieten uns eine elegantere und lesbarere Möglichkeit, den Ablauf unseres Programms zu sehen.

doSomething() .then(doSomethingElse) // and if you wouldn't mind .catch(anyErrorsPlease);

Writing our own promise: Goldilocks, the Three Bears, and a Supercomputer

Imagine you found a bowl of soup. You’d like to know the temperature of that soup before you eat it. You're out of thermometers, but luckily, you have access to a supercomputer that tells you the temperature of the bowl of soup. Unfortunately, this supercomputer can take up to 10 seconds to get the results.

Here are a couple of things to notice.

  1. We initiate a global variable called result.
  2. We simulate the duration of the network delay with Math.random() and setTimeout().
  3. We simulate a temperature with Math.random().
  4. We keep the delay and temperature values confined within a range by adding some extra “math”. The range for temp is 1 to 300; the range for delay is 1000ms to 10000ms (1s to 10 seconds).
  5. We log the delay and temperature so we have an idea of how long this function will take and the results we expect to see when it’s done.

Run the function and log the results.

getTemperature(); console.log(results); // undefined

The temperature is undefined. What happened?

The function will take a certain amount of time to run. The variable is not set until the delay is over. So while we run the function, setTimeout is asynchronous. The part of the code in setTimeout moves out of the main thread into a waiting area.

I have an article here that dives deeper into this process: Thrown For a Loop: Understanding Loops and Timeouts in JavaScript.

Since the part of our function that sets the variable result moves into a holding area until it is done, our parser is free to move onto the next line. In our case, it’s our console.log(). At this point, result is still undefined since our setTimeout is not over.

So what else could we try? We could run getTemperature() and then wait 11 seconds (since our max delay is ten seconds) and then console.log the results.

getTemperature(); setTimeout(() => { console.log(result); }, 11000); // Too Hot | Delay: 3323 | Temperature: 209 deg

This works, but the problem with this technique is, although in our example we know the maximum network delay, in a real-life example it might occasionally take longer than ten seconds. And, even if we could guarantee a maximum delay of ten seconds, if the result is ready sooner, we are wasting time.

Promises to the Rescue

We are going to refactor our getTemperature() function to return a promise. And instead of setting the result, we will reject the promise unless the result is “Just Right,” in which case we will resolve the promise. In either case, we will pass in some values to both resolve and reject.

We can now use the results of our promise we are returning (also know as consuming the promise).

getTemperature() .then(result => console.log(result)) .catch(error => console.log(error)); // Reject: Too Cold | Delay: 7880 | Temperature: 43 deg

.then will get called when our promise resolves and will return whatever information we pass into resolve.

.catch will get called when our promise rejects and will return whatever information we pass into reject.

Most likely, you’ll consume promises more than you will create them. In either case, they help make our code more elegant, readable, and efficient.

Summary

  1. Promises are objects that contain information about the completion of some asynchronous code and any resulting values we want to pass in.
  2. To return a promise we use return new Promise((resolve, reject)=> {})
  3. To consume a promise we use .then to get the information from a promise that has resolved, and .catch to get the information from a promise that has rejected.
  4. You’ll probably use (consume) promises more than you’ll write.

References

1.) //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise