Eric Elliott

Folgen

Jan 23, 2017 · 11 min lesen

Foto von Kabun (CC VON NC SA 2.,0)

“ Master the JavaScript Interview “ ist eine Reihe von Beiträgen, die Kandidaten auf häufige Fragen vorbereiten sollen, denen sie wahrscheinlich begegnen werden, wenn sie sich für eine JavaScript-Position auf mittlerer bis hoher Ebene bewerben. Dies sind Fragen, die ich häufig in echten Interviews verwende.

Ein promise ist ein Objekt, das irgendwann in der Zukunft einen einzelnen Wert erzeugen kann: entweder einen aufgelösten Wert oder einen Grund, dass es nicht aufgelöst wurde (z. B. ist ein Netzwerkfehler aufgetreten)., Ein Versprechen kann sich in einem von drei möglichen Zuständen befinden: erfüllt, abgelehnt oder anhängig. Versprechen Benutzer können Rückrufe anhängen, um den erfüllten Wert oder den Grund für die Ablehnung zu behandeln.

Versprechen sind eifrig, was bedeutet, dass ein Versprechen beginnt, jede Aufgabe zu erledigen, die Sie ihm geben, sobald der Versprechen-Konstruktor aufgerufen wird. Wenn Sie Hilfe benötigen, schauen Sie sich Observablen oder Aufgaben an.

Eine unvollständige Geschichte von Versprechungen

Frühe Implementierungen von Versprechungen und Futures (eine ähnliche / verwandte Idee) tauchten bereits in den 1980er Jahren in Sprachen wie MultiLisp und Concurrent Prolog auf., Die Verwendung des Wortes „Versprechen“ wurde 1988 von Barbara Liskov und Liuba Shrira geprägt.

Als ich das erste Mal von Versprechungen in JavaScript hörte, war Node brandneu und die Community diskutierte über den besten Umgang mit asynchronem Verhalten. Die Community experimentierte eine Weile mit Versprechungen, entschied sich jedoch schließlich für die Node-Standard Error-First-Rückrufe.

Ungefähr zur gleichen Zeit fügte Dojo Versprechungen über die verzögerte API hinzu. Wachsendes Interesse und Aktivität führten schließlich zu den neu gebildeten Promises / A-Spezifikationen, die verschiedene Versprechen interoperabler machen sollten.,

Das asynchrone Verhalten von jQuery wurde um Versprechen herum umgestaltet. jQuery Versprechen Unterstützung hatte bemerkenswerte Ähnlichkeiten mit Dojos Versprechen, und es wurde schnell die am häufigsten verwendete Versprechen Implementierung in JavaScript aufgrund jQuery immense Popularität — für eine Zeit. Es unterstützte jedoch nicht das Verkettungsverhalten mit zwei Kanälen (erfüllt/abgelehnt) & exception management, auf das die Leute zählten, um Tools auf Versprechen aufzubauen.,

Trotz dieser Schwächen machte jQuery offiziell JavaScript-Versprechen zum Mainstream und bessere Stand-Alone-Promise-Bibliotheken wie Q, When und Bluebird wurden sehr beliebt. Die Implementierungsunverträglichkeiten von jQuery haben einige wichtige Klarstellungen in der Promise-Spezifikation ausgelöst, die als Promises/A+ – Spezifikation neu geschrieben und umbenannt wurde.,

ES6 brachte eine Promises/A+ – konforme Promise global, und einige sehr wichtige APIs wurden auf der neuen Standard-Promise-Unterstützung aufgebaut: insbesondere die WHATWG Fetch-Spezifikation und der Async Functions-Standard (ein Entwurf der Stufe 3 zum Zeitpunkt dieses Schreibens).

Die hier beschriebenen Versprechungen sind mit der Spezifikation Promises/A+ kompatibel, wobei der Fokus auf der Implementierung des ECMAScript-Standards Promise liegt.

Wie Versprechen funktionieren

Ein Versprechen ist ein Objekt, das synchron von einer asynchronen Funktion zurückgegeben werden kann., Es befindet sich in einem von drei möglichen Zuständen:

Ein Versprechen wird erfüllt, wenn es nicht anhängig ist (es wurde aufgelöst oder abgelehnt). Manchmal benutzen Leute aufgelöst und angesiedelt, um dasselbe zu bedeuten: nicht anhängig.

Einmal abgewickelt, kann ein Versprechen nicht umgesiedelt werden. Der erneute Aufruf von resolve() oder reject() hat keine Auswirkungen. Die Unveränderlichkeit eines besicherten Versprechens ist ein wichtiges Merkmal.

Native JavaScript-Versprechungen machen Versprechungszustände nicht verfügbar. Stattdessen wird erwartet, dass Sie das Versprechen als Blackbox behandeln., Nur die Funktion, die für das Erstellen des Versprechens verantwortlich ist, kennt den Versprechungsstatus oder Zugriff zum Auflösen oder Ablehnen.

Hier ist eine Funktion, die ein Versprechen zurückgibt, das nach einer bestimmten Zeitverzögerung aufgelöst wird:

wait — promise Beispiel für CodePen

Unser wait(3000) Aufruf wartet 3000ms (3 Sekunden), und dann log 'Hello!'., Alle spec-kompatiblen Versprechen definieren eine .then() – Methode, mit der Sie Handler übergeben, die den aufgelösten oder abgelehnten Wert annehmen können.

Der ES6 promise Konstruktor nimmt eine Funktion an. Diese Funktion verwendet zwei Parameter, resolve() und reject(). Im obigen Beispiel verwenden wir nur resolve(), also habe ich reject() aus der Parameterliste gelassen. Dann rufen wir setTimeout() auf, um die Verzögerung zu erstellen, und rufen resolve() auf, wenn es fertig ist.,

Optional können Sie resolve() oder reject() mit Werten versehen, die an die mit .then()verbundenen Rückruffunktionen übergeben werden.

Wenn ich reject() mit einem Wert übergebe, übergebe ich immer ein Error Objekt. Im Allgemeinen möchte ich zwei mögliche Auflösungszustände: den normalen Happy Path oder eine Ausnahme — alles, was den normalen Happy Path daran hindert, zu geschehen. Das Übergeben einesError – Objekts macht dies explizit.,

Wichtige Promise Rules

Ein Standard für Promises wurde von der Promises / A+ specification Community definiert. Es gibt viele Implementierungen, die dem Standard entsprechen, einschließlich der Versprechen des JavaScript-Standards ECMAScript.

Versprechen nach der Spezifikation muss ein bestimmter Satz von Regeln folgen:

  • Ein Versprechen oder „thenable“ist ein Objekt, das eine standardkonforme .then() Methode liefert.
  • Ein ausstehendes Versprechen kann in einen erfüllten oder abgelehnten Zustand übergehen.,
  • Ein erfülltes oder abgelehntes Versprechen wird erfüllt und darf nicht in einen anderen Zustand übergehen.
  • Sobald ein Versprechen erfüllt ist, muss es einen Wert haben (der undefined). Dieser Wert darf sich nicht ändern.

Änderung bezieht sich in diesem Zusammenhang auf identity (===) Vergleich. Ein Objekt kann als erfüllter Wert verwendet werden, und Objekteigenschaften können mutieren.,

Jedes Versprechen muss eine .then() – Methode mit folgender Signatur angeben:

promise.then(
onFulfilled?: Function,
onRejected?: Function
) => Promise

Die .then() – Methode muss diese Regeln erfüllen:

  • Sowohl onFulfilled() als auch onRejected() sind optional.
  • Wenn die angegebenen Argumente keine Funktionen sind, müssen sie ignoriert werden.
  • onFulfilled() wird aufgerufen, nachdem das Versprechen erfüllt wurde, wobei der Wert des Versprechens das erste Argument ist.,
  • onRejected() wird aufgerufen, nachdem das Versprechen abgelehnt wurde, mit dem Grund für die Ablehnung als erstes Argument. Der Grund kann ein gültiger JavaScript-Wert sein, aber da Ablehnungen im Wesentlichen gleichbedeutend mit Ausnahmen sind, empfehle ich die Verwendung von Fehlerobjekten.
  • Weder onFulfilled() noch onRejected() dürfen mehr als einmal aufgerufen werden.
  • .then() kann viele Male auf dem gleichen Versprechen aufgerufen werden. Mit anderen Worten, ein Versprechen kann verwendet werden, um Rückrufe zu aggregieren.,
  • .then() muss ein neues Versprechen zurückgeben, promise2.
  • Wenn onFulfilled() oder onRejected() Rückgabe eines Wertes x und x ist ein Versprechen, promise2 wird die Sperre in der mit (angenommen, die gleichen Zustand und den Wert als) x. Andernfalls wird promise2 mit dem Wert xerfüllt.,
  • Wenn entweder onFulfilled oder onRejected wirft eine exception e, promise2 müssen abgelehnt werden mit e als Grund.
  • Wenn onFulfilled keine Funktion ist und promise1 erfüllt ist, muss promise2 mit dem gleichen Wert wie promise1erfüllt sein.,
  • Wenn onRejected keine Funktion ist und promise1 abgelehnt wird, muss promise2 aus demselben Grund wie promise1abgelehnt werden.

Promise Chaining

Da .then() immer ein neues Versprechen zurückgibt, ist es möglich, Versprechen mit genauer Kontrolle darüber zu verketten, wie und wo Fehler behandelt werden. Versprechungen ermöglichen es Ihnen, das Verhalten von try/catch normalen synchronen Code nachzuahmen.,

Wie synchroner Code führt die Verkettung zu einer Sequenz, die seriell ausgeführt wird., Mit anderen Worten, Sie können Folgendes tun:

fetch(url)
.then(process)
.then(save)
.catch(handleErrors)
;

Hier ist ein Beispiel für eine komplexe Versprechungskette mit mehreren Ablehnungen:

Promise chaining behavior example on CodePen

Error Handling

Beachten Sie, dass Versprechungen sowohl einen Erfolg als auch einen Fehlerbehandler haben, und es ist sehr üblich, Code zu sehen, der dies tut:

save().then(
handleSuccess,
handleError
);

Aber was passiert, wenn handleSuccess() einen Fehler auslöst?, Das von .then() zurückgegebene Versprechen wird abgelehnt, aber es gibt nichts, was die Ablehnung abfangen könnte — was bedeutet, dass ein Fehler in Ihrer App verschluckt wird. Hoppla!

Aus diesem Grund betrachten einige Leute den obigen Code als Anti-Muster und empfehlen stattdessen Folgendes:

save()
.then(handleSuccess)
.catch(handleError)
;

Der Unterschied ist subtil, aber wichtig. Im ersten Beispiel wird ein Fehler mit Ursprung in der save() – Operation abgefangen, aber ein Fehler mit Ursprung in der handleSuccess() – Funktion wird verschluckt.,

Without .catch(), an error in the success handler is uncaught.

In the second example, .catch() will handle rejections from either save(), or handleSuccess().

With .catch(), both error sources are handled., (Diagrammquelle)

Natürlich kann der save() – Fehler ein Netzwerkfehler sein, während der handleSuccess() – Fehler möglicherweise darauf zurückzuführen ist, dass der Entwickler vergessen hat, einen bestimmten Statuscode zu behandeln. Was ist, wenn Sie anders damit umgehen wollen? Sie können sich für beide entscheiden:

save()
.then(
handleSuccess,
handleNetworkError
)
.catch(handleProgrammerError)
;

Was auch immer Sie bevorzugen, ich empfehle, alle Versprechungsketten mit einer .catch(). Das lohnt sich zu wiederholen:

Ich empfehle, alle Versprechungsketten mit einer .catch()zu beenden.,

Wie storniere ich ein Versprechen?

Eines der ersten Dinge, über die sich neue Versprechungsbenutzer oft wundern, ist, wie sie ein Versprechen stornieren können. Hier ist eine Idee: Lehnen Sie das Versprechen einfach mit „Storniert“ als Grund ab. Wenn Sie anders damit umgehen müssen als mit einem „normalen“ Fehler, führen Sie Ihre Verzweigung in Ihrem Fehlerhandler durch.

Hier sind einige häufige Fehler, die Menschen machen, wenn sie ihre eigenen Versprechen Stornierung rollen:

Hinzufügen .,cancel () zum Versprechen

Das Hinzufügen von .cancel() macht das Versprechen nicht zum Standard, verstößt jedoch auch gegen eine andere Regel von Versprechungen: Nur die Funktion, die das Versprechen erstellt, sollte in der Lage sein, das Versprechen aufzulösen, abzulehnen oder abzubrechen. Das Aussetzen bricht diese Kapselung und ermutigt die Leute, Code zu schreiben, der das Versprechen an Orten manipuliert, die nichts darüber wissen sollten. Vermeiden Sie Spaghetti und gebrochene Versprechen.

Vergessen zu bereinigen

Einige kluge Leute haben herausgefunden, dass es eine Möglichkeit gibt, Promise.race() als Löschmechanismus zu verwenden., Das Problem dabei ist, dass die Löschungssteuerung von der Funktion übernommen wird, die das Versprechen erstellt, und dies ist der einzige Ort, an dem Sie ordnungsgemäße Bereinigungsaktivitäten durchführen können, z. B. das Löschen von Timeouts oder das Freigeben von Speicher durch Löschen von Verweisen auf Daten usw…

Vergessen, ein abgelehntes Abbrechen-Versprechen zu verarbeiten

Wussten Sie, dass Chrome Warnmeldungen über die gesamte Konsole auslöst, wenn Sie vergessen, eine Versprechen-Ablehnung zu behandeln? Hoppla!

Zu komplex

Der zurückgenommene TC39-Stornierungsvorschlag schlug einen separaten Nachrichtenkanal für Stornierungen vor., Es wurde auch ein neues Konzept namens Cancellation Token verwendet. Meiner Meinung nach hätte die Lösung die Promise-Spezifikation erheblich aufgebläht, und das einzige Merkmal, das dafür gesorgt hätte, dass Spekulationen die Trennung von Ablehnungen und Stornierungen nicht direkt unterstützen, ist die Trennung von Ablehnungen und Stornierungen, die IMO zunächst nicht notwendig ist.

Möchten Sie wechseln, je nachdem, ob eine Ausnahme oder eine Stornierung vorliegt? Ja, absolut. Ist das der Job des Versprechens? Meiner Meinung nach, nein, ist es nicht.,

Promise Cancellation überdenken

Im Allgemeinen übergebe ich alle Informationen, die das Versprechen benötigt, um zu bestimmen, wie es zum Zeitpunkt der Promise-Erstellung aufgelöst / abgelehnt / abgebrochen werden soll. Auf diese Weise ist keine .cancel() – Methode für ein Versprechen erforderlich. Sie fragen sich vielleicht, wie Sie möglicherweise wissen könnten, ob Sie zur Erstellungszeit abbrechen werden oder nicht.

“ Wenn ich noch nicht weiß, ob ich abbrechen soll oder nicht, woher weiß ich, was ich beim Erstellen des Versprechens weitergeben soll?,“

Wenn es nur eine Art Objekt gäbe, das in Zukunft für einen potenziellen Wert stehen könnte… oh, warte.

Der Wert, den wir übergeben, um darzustellen, ob abgebrochen werden soll oder nicht, könnte selbst ein Versprechen sein. So könnte das aussehen:

Cancellable wait — probieren Sie es aus CodePen

Wir verwenden die Standardparameterzuweisung, um anzuweisen, dass sie nicht standardmäßig abgebrochen werden soll. Dadurch ist der Parameter cancel praktisch optional., Dann setzen wir das Timeout wie zuvor, aber dieses Mal erfassen wir die ID des Timeouts, damit wir es später löschen können.

Wir verwenden diecancel.then() – Methode, um die Stornierung und Ressourcenbereinigung zu behandeln. Dies wird nur ausgeführt, wenn das Versprechen abgebrochen wird, bevor es aufgelöst werden kann. Wenn Sie zu spät stornieren, haben Sie Ihre Chance verpasst. Der Zug hat den Bahnhof verlassen.

Hinweis: Sie fragen sich vielleicht, wofür die noop() – Funktion gedacht ist. Das Wort noop steht für no-op und bedeutet eine Funktion, die nichts tut., Ohne sie löst V8 Warnungen aus: UnhandledPromiseRejectionWarning: Unhandled promise rejection. Es ist eine gute Idee, Versprechen immer abzulehnen, auch wenn Ihr Handler eine noop().

Abstraktion Versprechen Widerrufs

Dies ist in Ordnung für ein wait() timer, aber wir können Abstrakt diese Idee weiter zu Kapseln, alles was Sie haben zu erinnern:

  1. Abweisen Abbrechen Versprechen durch Standard, wir don nicht wollen zu stornieren oder zu werfen Fehler, wenn kein Abbrechen Versprechen bekommt übergeben.
  2. Denken Sie daran, die Bereinigung durchzuführen, wenn Sie Stornierungen ablehnen.,
  3. Denken Sie daran, dass die onCancel Bereinigung selbst einen Fehler auslösen kann und dieser Fehler auch behandelt werden muss. (Beachten Sie, dass die Fehlerbehandlung im obigen Wait-Beispiel weggelassen wird — es ist leicht zu vergessen!)

Lassen Sie uns ein cancellable promise Utility erstellen, mit dem Sie jedes Versprechen umbrechen können., Um beispielsweise Netzwerkanforderungen usw. zu verarbeiten, sieht die Signatur folgendermaßen aus:

speculation(fn: SpecFunction, shouldCancel: Promise) => Promise

Die SpecFunction ist genau wie die Funktion, die Sie an den Promise Konstruktor übergeben würden, mit einer Ausnahme — sie benötigt eine onCancel() Handler:

SpecFunction(resolve: Function, reject: Function, onCancel: Function) => Void

Beachten Sie, dass dieses Beispiel nur eine Illustration ist, um Ihnen den Kern der Funktionsweise zu geben. Es gibt einige andere Randfälle, die Sie berücksichtigen müssen., In dieser Version wird beispielsweise handleCancel aufgerufen, wenn Sie das Versprechen abbrechen, nachdem es bereits abgeschlossen ist.

Ich habe eine gepflegte Produktionsversion davon implementiert, wobei Edge-Fälle als Open-Source-Bibliothek behandelt werden, Spekulation.

Verwenden wir die verbesserte Bibliotheksabstraktion, um das Dienstprogramm cancellable wait() von zuvor neu zu schreiben., Installieren Sie zuerst die Spekulation:

npm install --save speculation

Jetzt können Sie sie importieren und verwenden:

Dies vereinfacht die Dinge ein wenig, da Sie sich keine Sorgen um die noop() machen müssen, in Ihrem onCancel(), Funktion oder andere Randfälle. Diese Details wurden von speculation()abstrahiert. Überprüfen Sie es und fühlen Sie sich frei, es in realen Projekten zu verwenden.,

Extras des nativen JS-Versprechens

Das nativePromise – Objekt enthält einige zusätzliche Dinge, an denen Sie interessiert sein könnten:

  • Promise.reject() gibt ein abgelehntes Versprechen zurück.
  • Promise.resolve() gibt ein Versprechen gelöst.
  • Promise.race() nimmt ein Array (oder ein beliebiges iterables) und gibt ein Versprechen zurück, das mit dem Wert des ersten aufgelösten Versprechens im iterable aufgelöst wird, oder lehnt es mit dem Grund des ersten Versprechens ab, das abgelehnt wird.,
  • Promise.all() nimmt ein Array (oder ein beliebiges iterables) und gibt ein Versprechen zurück, das aufgelöst wird, wenn alle Versprechen im iterablen Argument aufgelöst sind, oder lehnt mit dem Grund des ersten übergebenen Versprechens ab, das abgelehnt wird.

Versprechungen sind zu einem integralen Bestandteil mehrerer Redewendungen in JavaScript geworden, einschließlich des WHATWG Fetch-Standards, der für die meisten modernen Ajax-Anforderungen verwendet wird, und des asynchronen Funktionsstandards, der verwendet wird, um asynchronen Code synchron aussehen zu lassen.,

Asynchrone Funktionen sind zum Zeitpunkt dieses Schreibens Stufe 3, aber ich gehe davon aus, dass sie bald eine sehr beliebte, sehr häufig verwendete Lösung für die asynchrone Programmierung in JavaScript werden — was bedeutet, dass das Lernen, Versprechen zu schätzen, für JavaScript-Entwickler in naher Zukunft noch wichtiger sein wird.

Wenn Sie beispielsweise Redux verwenden, empfehle ich Ihnen, redux-saga: Eine Bibliothek zum Verwalten von Nebenwirkungen in Redux, die in der gesamten Dokumentation von asynchronen Funktionen abhängt.,

Ich hoffe, dass selbst erfahrene Promise-Benutzer besser verstehen, was Versprechen sind und wie sie funktionieren und wie sie sie nach dem Lesen besser nutzen können.

Entdecken Sie die Serie

  • Was ist ein Abschluss?
  • Was ist der Unterschied zwischen Klassen-und Prototypenvererbung?
  • Was ist eine Reine Funktion?
  • Was ist Funktionszusammensetzung?
  • Was ist Funktionale Programmierung?
  • Was ist ein Versprechen?
  • Soft Skills

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.