Elegante asynchrone Programmierung mit Versprechen

Guten Tag, Freunde!

Versprechen (Versprechen) sind eine relativ neue Funktion von JavaScript, mit der Sie die Ausführung einer Aktion bis zum Abschluss der vorherigen Aktion verzögern oder auf die nicht erfolgreiche Ausführung der Aktion reagieren können. Dies trägt zur korrekten Bestimmung der Abfolge von asynchronen Operationen bei. In diesem Artikel wird erläutert, wie Versprechen funktionieren, wie sie in der Web-API verwendet werden und wie Sie Ihr eigenes Versprechen schreiben können.

Bedingungen: Grundlegende Computerkenntnisse, Kenntnisse der Grundlagen von JS.
Ziel: Verstehen, was Versprechen sind und wie sie verwendet werden.

Was sind Versprechen?


Wir haben die Versprechen im ersten Artikel des Kurses kurz besprochen , hier werden wir sie genauer betrachten.

Im Wesentlichen ist ein Versprechen ein Objekt, das einen Zwischenzustand einer Operation darstellt - es „verspricht“, dass das Ergebnis in Zukunft zurückgegeben wird. Es ist nicht genau bekannt, wann der Vorgang abgeschlossen und wann das Ergebnis zurückgegeben wird. Es besteht jedoch die Garantie, dass Ihr Code nach Abschluss des Vorgangs entweder etwas mit dem Ergebnis tut oder den Fehler ordnungsgemäß behandelt.

In der Regel ist es für uns weniger interessant, wie viel Zeit ein asynchroner Vorgang benötigt (nicht zu lange!) Als die Möglichkeit einer sofortigen Reaktion auf seinen Abschluss. Und natürlich ist es schön zu wissen, dass der Rest des Codes nicht blockiert ist.

Eines der häufigsten Versprechen sind Web-APIs, die Versprechen zurückgeben. Schauen wir uns eine hypothetische Video-Chat-Anwendung an. Die Anwendung verfügt über ein Fenster mit einer Liste der Freunde des Benutzers. Durch Klicken auf die Schaltfläche neben dem Namen (oder Avatar) des Benutzers wird ein Videoanruf gestartet.

Der Button-Handler ruft getUserMedia () aufum auf die Kamera und das Mikrofon des Benutzers zuzugreifen. Von dem Moment an, in dem getUserMedia () den Benutzer um Erlaubnis bittet (Geräte zu verwenden, welches Gerät zu verwenden ist, wenn der Benutzer mehrere Mikrofone oder Kameras hat, unter anderem nur einen Sprachanruf), erwartet getUserMedia () nicht nur die Entscheidung des Benutzers, sondern auch die Freigabe Geräte, wenn sie derzeit verwendet werden. Darüber hinaus reagiert der Benutzer möglicherweise nicht sofort. All dies kann zu großen Zeitverzögerungen führen.

Wenn vom Hauptthread eine Anforderung zur Berechtigung zur Verwendung von Geräten gestellt wird, wird der Browser blockiert, bis getUserMedia () abgeschlossen ist. Es ist inakzeptabel. Ohne Versprechen wird alles im Browser „nicht anklickbar“, bis der Benutzer die Erlaubnis zur Verwendung des Mikrofons und der Kamera erteilt. Anstatt auf die Entscheidung eines Benutzers zu warten und einen MediaStream für einen aus Quellen (Kamera und Mikrofon) erstellten Stream zurückzugeben, gibt getUserMedia () daher das Versprechen zurück, dass MediaStream verarbeitet, sobald er verfügbar ist.

Der Video-Chat-Anwendungscode könnte folgendermaßen aussehen:

function handle CallButton(evt){
    setStatusMessage('Calling...')
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(chatStream => {
        selfViewElem.srcObject = chatStream
        chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream))
        setStatusMessage('Connected')
    }).catch(err => {
        setStatusMessage('Failed to connect')
    })
}

Die Funktion beginnt mit dem Aufruf von setStatusMessage () und der Meldung 'Calling ...', die als Indikator dafür dient, dass versucht wird, einen Anruf zu tätigen. Anschließend wird getUserMedia () aufgerufen und ein Stream angefordert, der Video- und Audiospuren enthält. Sobald der Stream erstellt wurde, wird ein Videoelement installiert, um den Stream von der Kamera mit der Bezeichnung "Selbstansicht" anzuzeigen. Zu WebRTC RTCPeerConnection , einer Verbindung zu einem anderen Benutzer , werden Audiospuren hinzugefügt . Danach wird der Status auf "Verbunden" aktualisiert.

Wenn getUserMedia () fehlschlägt, wird der catch-Block gestartet. Es verwendet setStatusMessage (), um eine Fehlermeldung anzuzeigen.

Beachten Sie, dass der Aufruf von getUserMedia () zurückgegeben wird, auch wenn der Videostream noch nicht empfangen wurde. Selbst wenn die Funktion handleCallButton () die Kontrolle über den Code zurückgibt, der sie aufgerufen hat, würde sie den Handler aufrufen, sobald getUserMedia () die Ausführung abgeschlossen hat. Bis die Anwendung „versteht“, dass die Übertragung begonnen hat, befindet sich getUserMedia () im Standby-Modus.

Hinweis: Weitere Informationen hierzu finden Sie im Artikel „Signale und Videoanrufe“ . Dieser Artikel enthält einen vollständigeren Code als den im Beispiel verwendeten.

Rückruffunktionen Problem


Um zu verstehen, warum Versprechen eine gute Lösung sind, ist es hilfreich, die alte Art des Schreibens von Rückrufen zu betrachten, um festzustellen, welche Probleme sie hatten.

Als Beispiel sollten Sie Pizza bestellen. Eine erfolgreiche Pizza-Bestellung besteht aus mehreren Schritten, die nacheinander ausgeführt werden müssen:

  1. Wählen Sie die Füllung. Dies kann einige Zeit dauern, wenn Sie lange darüber nachdenken, und kann fehlschlagen, wenn Sie Ihre Meinung ändern und ein Curry bestellen.
  2. Wir geben eine Bestellung auf. Das Kochen von Pizza dauert einige Zeit und kann fehlschlagen, wenn dem Restaurant die erforderlichen Zutaten fehlen.
  3. Wir bekommen Pizza und essen. Der Empfang von Pizza kann fehlschlagen, wenn wir beispielsweise die Bestellung nicht bezahlen können.

Ein Pseudocode alten Stils mit Rückruffunktionen könnte folgendermaßen aussehen:

chooseToppings(function(toppings){
    placeOrder(toppings, function(order){
        collectOrder(order, function(pizza){
            eatPizza(pizza)
        }, failureCallback)
    }, failureCallback)
}, failureCallback)

Ein solcher Code ist schwer zu lesen und zu pflegen (er wird oft als "Hölle der Rückrufe" oder "Hölle der Rückrufe" bezeichnet). Die Funktion failCallback () muss auf jeder Verschachtelungsebene aufgerufen werden. Es gibt andere Probleme.

Wir verwenden Versprechen


Versprechen machen den Code sauberer, verständlicher und unterstützender. Wenn wir den Pseudocode mit asynchronen Versprechungen umschreiben, erhalten wir Folgendes:

chooseToppings()
.then(function(toppings){
    return placeOrder(toppings)
})
.then(function(order){
    return collectOrder(order)
})
.then(function(pizza){
    eatPizza(pizza)
})
.catch(failureCallback)

Das ist viel besser - wir sehen, was passiert, wir verwenden einen .catch () -Block, um alle Fehler zu behandeln, die Funktion blockiert nicht den Hauptstrom (so können wir Videospiele spielen, während wir auf Pizza warten), jeder Vorgang wird garantiert ausgeführt, nachdem der vorherige abgeschlossen ist. Da jedes Versprechen ein Versprechen zurückgibt, können wir die .then-Kette verwenden. Großartig, richtig?

Mit Hilfe von Pfeilfunktionen kann der Pseudocode weiter vereinfacht werden:

chooseToppings()
.then(toppings =>
    placeOrder(toppings)
)
.then(order =>
    collectOrder(order)
)
.then(pizza =>
    eatPizza(pizza)     
)
.catch(failureCallback)

Oder sogar so:

chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback)

Dies funktioniert, weil () => x identisch mit () => {return x} ist.

Sie können dies sogar tun (da Funktionen einfach Parameter übergeben, benötigen wir keine Überlagerung):

chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback)

Ein solcher Code ist schwieriger zu lesen und kann nicht mit komplexeren Konstrukten als im Pseudocode verwendet werden.

Hinweis: Pseudocode kann mit async / await noch verbessert werden, was in einem zukünftigen Artikel erläutert wird.

Im Kern ähneln Versprechen den Hörern von Events, weisen jedoch einige Unterschiede auf:

  • Ein Versprechen wird nur einmal erfüllt (Erfolg oder Misserfolg). Es kann nicht zweimal ausgeführt werden oder nach Abschluss des Vorgangs von Erfolg zu Misserfolg oder umgekehrt wechseln.
  • Wenn das Versprechen erfüllt ist und wir eine Rückruffunktion hinzufügen, um das Ergebnis zu verarbeiten, wird diese Funktion aufgerufen, obwohl das Ereignis vor dem Hinzufügen der Funktion aufgetreten ist.

Grundlegende Versprechenssyntax: ein echtes Beispiel


Das Wissen um Versprechen ist wichtig, da viele Web-APIs sie in Funktionen verwenden, die potenziell komplexe Aufgaben ausführen. Die Arbeit mit modernen Webtechnologien erfordert die Verwendung von Versprechungen. Später werden wir lernen, wie wir unsere eigenen Versprechen schreiben, aber jetzt schauen wir uns einige einfache Beispiele an, die in der Web-API zu finden sind.

Im ersten Beispiel verwenden wir die Methode fetch () , um das Bild aus dem Netzwerk abzurufen , die Methode blob () , um den Inhalt des Antwortkörpers in ein Blob- Objekt zu konvertieren und dieses Objekt im <img> -Element anzuzeigen . Dieses Beispiel ist dem Beispiel aus dem ersten Artikel sehr ähnlich , aber wir werden es etwas anders machen.

Hinweis: Das folgende Beispiel funktioniert nicht, wenn Sie es einfach aus einer Datei ausführen (d. H. Mit file: // URL). Sie müssen es über einen lokalen Server ausführen oder Online-Lösungen wie Glitch- oder GitHub-Seiten verwenden .

1. Laden Sie zunächst den HTML-Code und das Bild hoch , die wir erhalten.

2. Fügen Sie das <script> -Element am Ende des <body> hinzu .

3. Fügen Sie im <script> -Element die folgende Zeile hinzu:

let promise = fetch('coffee.jpg')

Die Methode fetch (), die die Bild-URL als Parameter verwendet, wird verwendet, um das Bild aus dem Netzwerk abzurufen. Als zweiten Parameter können wir ein Objekt mit Einstellungen angeben, beschränken uns jedoch vorerst auf eine einfache Option. Wir speichern das von fetch () zurückgegebene Versprechen in der Versprechen-Variablen. Wie bereits erwähnt, ist ein Versprechen ein Objekt, das einen Zwischenstaat darstellt - der offizielle Name für einen bestimmten Staat steht noch aus.

4. Um mit dem Ergebnis des erfolgreichen Abschlusses des Versprechens zu arbeiten (in unserem Fall, wenn Response zurückkehrt ), rufen wir die Methode .then () auf. Die Rückruffunktion im Block .then () (oft als Executor bezeichnet) wird nur gestartet, wenn das Versprechen erfolgreich abgeschlossen und das Antwortobjekt zurückgegeben wurde. Sie geben an, dass das Versprechen erfüllt wurde (erfüllt, erfüllt). Die Antwort wird als Parameter übergeben.

Hinweis. Die Operation des Blocks .then () ähnelt der Operation des Ereignis-Listeners AddEventListener (). Sie wird erst ausgelöst, nachdem das Ereignis eingetreten ist (nachdem das Versprechen erfüllt wurde). Der Unterschied zwischen beiden besteht darin, dass .then () nur einmal aufgerufen werden kann, während der Listener für mehrere Aufrufe ausgelegt ist.

Nach Erhalt der Antwort rufen wir die blob () -Methode auf, um die Antwort in ein Blob-Objekt zu konvertieren. Es sieht aus wie das:

response => response.blob()

... das ist ein kurzer Eintrag für:

function (response){
    return response.blob()
}

OK, genug Worte. Fügen Sie nach der ersten Zeile Folgendes hinzu:

let promise2 = promise.then(response => response.blob())

5. Jeder Aufruf von .then () schafft ein neues Versprechen. Dies ist sehr nützlich: Da blob () auch ein Versprechen zurückgibt, können wir das Blob-Objekt verarbeiten, indem wir beim zweiten Versprechen .then () aufrufen. Da wir etwas komplizierteres tun möchten, als die Methode aufzurufen und das Ergebnis zurückzugeben, müssen wir den Funktionskörper in geschweifte Klammern setzen (andernfalls wird eine Ausnahme ausgelöst):

Fügen Sie am Ende des Codes Folgendes hinzu:

let promise3 = promise2.then(myBlob => {

})

6. Füllen wir den Hauptteil der ausführenden Funktion aus. Fügen Sie den geschweiften Klammern die folgenden Zeilen hinzu:

let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)

Hier rufen wir die Methode URL.createObjectURL () auf und übergeben sie als Blob-Parameter, der das zweite Versprechen zurückgibt. Wir bekommen einen Link zum Objekt. Dann erstellen wir ein <img> -Element, setzen das src-Attribut mit dem Wert der Objektverknüpfung und fügen es dem DOM hinzu, sodass das Bild auf der Seite angezeigt wird.

Wenn Sie den HTML-Code speichern und in einen Browser laden, wird das Bild wie erwartet angezeigt. Ausgezeichnete Arbeit!

Hinweis. Sie haben wahrscheinlich bemerkt, dass diese Beispiele etwas erfunden sind. Sie können auf die Methoden fetch () und blob () verzichten und die entsprechende URL <img>, Coffee.jpg, zuweisen. Wir sind diesen Weg gegangen, um anhand eines einfachen Beispiels die Arbeit mit Versprechungen zu demonstrieren.

Fehlerreaktion


Wir haben etwas vergessen - wir haben keinen Fehlerbehandler, falls eines der Versprechen fehlschlägt (wird abgelehnt). Wir können einen Fehlerhandler mit der Methode .catch () hinzufügen. Lass uns das tun:

let errorCase = promise3.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Um dies in Aktion zu sehen, geben Sie die falsche Bild-URL an und laden Sie die Seite neu. In der Konsole wird eine Fehlermeldung angezeigt.

In diesem Fall wird eine Fehlermeldung auch in der Konsole angezeigt, wenn wir den Block .catch () nicht hinzufügen. Mit .catch () können wir Fehler jedoch so behandeln, wie wir es möchten. In einer realen Anwendung versucht Ihr .catch () -Block möglicherweise, das Bild erneut aufzunehmen oder das Standardbild anzuzeigen, oder fordert den Benutzer auf, ein anderes Bild auszuwählen oder etwas anderes zu tun.

Hinweis. Sehen Sie sich eine Live-Demo ( Quellcode ) an.

Blockzusammenführung


Tatsächlich ist der Ansatz, mit dem wir den Code geschrieben haben, nicht optimal. Wir sind bewusst diesen Weg gegangen, damit Sie verstehen, was in jeder Phase passiert. Wie bereits gezeigt, können wir .then () -Blöcke (und .catch () -Blöcke) kombinieren. Unser Code kann wie folgt umgeschrieben werden (siehe auch simple-fetch-chained.html auf GitHub):

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Denken Sie daran, dass der vom Versprechen zurückgegebene Wert als Parameter an den nächsten Funktionsausführenden des Blocks .then () übergeben wird.

Hinweis. Die .then () /. Catch () -Blöcke in Versprechungen sind asynchrone Äquivalente des synchronen try ... catch-Blocks. Denken Sie daran: Synchroner Versuch ... catch funktioniert nicht in asynchronem Code.

Promise Terminologie Schlussfolgerungen


Lassen Sie uns eine Bestandsaufnahme machen und eine kurze Anleitung schreiben, die Sie in Zukunft verwenden können. Um das Wissen zu festigen, empfehlen wir, den vorherigen Abschnitt mehrmals zu lesen.

1. Wenn das Versprechen erstellt wird, sagen sie, dass es sich in einem Erwartungszustand befindet.
2. Wenn das Versprechen zurückgegeben wird, sagen sie, dass es erfüllt (gelöst) wurde:

  1. 1. Ein erfolgreich abgeschlossenes Versprechen wird als erfüllt bezeichnet. Es gibt den Wert zurück, der über die Kette von .then () am Ende des Versprechens erhalten werden kann. Die ausführende Funktion im Block .then () enthält den vom Versprechen zurückgegebenen Wert.
  2. 2. Ein nicht erfolgreich abgeschlossenes Versprechen wird als abgelehnt bezeichnet. Es gibt den Grund zurück, die Fehlermeldung, die zur Ablehnung des Versprechens geführt hat. Dieser Grund kann über den Block .catch () am Ende des Versprechens ermittelt werden.

Führen Sie den Code nach mehreren Versprechungen aus


Wir haben die Grundlagen der Verwendung von Versprechungen gelernt. Schauen wir uns nun die erweiterten Funktionen an. Die Kette von .then () ist in Ordnung, aber was ist, wenn wir mehrere Versprechensblöcke nacheinander aufrufen wollen?

Sie können dies mit der Standardmethode Promise.all () tun. Diese Methode verwendet ein Array von Versprechungen als Parameter und gibt ein neues Versprechen zurück, wenn alle Versprechungen im Array erfüllt sind. Es sieht ungefähr so ​​aus:

Promise.all([a,b,c]).then(values => {
        ...
})

Wenn alle Versprechen erfüllt sind, wird der Array-Executor des Blocks .then () als Parameter übergeben. Wenn mindestens eines der Versprechen nicht erfüllt wird, wird der gesamte Block abgelehnt.

Dies kann sehr hilfreich sein. Stellen Sie sich vor, wir erhalten Informationen zum dynamischen Füllen der Benutzeroberfläche mit Inhalten auf unserer Seite. In vielen Fällen ist es sinnvoller, alle Informationen zu erhalten und später auf der Seite anzuzeigen, als die Informationen in Teilen anzuzeigen.

Schauen wir uns ein anderes Beispiel an:
1. Laden Sie eine Seitenvorlage herunter und platzieren Sie das <script> -Tag vor dem schließenden </ body> -Tag.

2. Laden Sie die Quelldateien herunter ( Coffee.jpg , Tea.jpg und Description.txt) oder ersetzen Sie sie durch Ihre.

3. Im Skript definieren wir zunächst eine Funktion, die die Versprechen zurückgibt, die wir an Promise.all () übergeben. Dies ist einfach, wenn wir Promise.all () nach Abschluss der drei Operationen fetch () ausführen. Wir können Folgendes tun:

let a = fetch(url1)
let b = fetch(url2)
let c = fetch(url3)

Promise.all([a, b, c]).then(values => {
    ...
})

Wenn das Versprechen erfüllt ist, enthält die Variable "values" drei Antwortobjekte, eines aus jeder abgeschlossenen fetch () - Operation.

Das wollen wir aber nicht. Es ist uns egal, wann die fetch () - Operationen abgeschlossen sind. Was wir wirklich wollen, sind die geladenen Daten. Dies bedeutet, dass wir den Block Promise.all () ausführen möchten, nachdem wir einen gültigen Blob erhalten haben, der Bilder und gültige Textzeichenfolgen darstellt. Wir können eine Funktion schreiben, die dies tut; Fügen Sie Ihrem <script> -Element Folgendes hinzu:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log('There has been a problem with your fetch operation ' + e.message)
    })
}

Es sieht etwas kompliziert aus, also gehen

wir den Code Schritt für Schritt durch: 1. Zunächst deklarieren wir die Funktion und übergeben ihr die URL und den Typ der resultierenden Datei.

2. Die Struktur der Funktion ähnelt der im ersten Beispiel - wir rufen die Funktion fetch () auf, um die Datei unter einer bestimmten URL abzurufen, und übertragen die Datei dann auf ein anderes Versprechen, das den dekodierten (gelesenen) Antworttext zurückgibt. Im vorherigen Beispiel war es immer die blob () -Methode.

3. Es gibt zwei Unterschiede:

  • Erstens hängt das zweite Rückgabeversprechen von der Art des Wertes ab. Innerhalb der ausführenden Funktion verwenden wir die if ... else if-Anweisung, um das Versprechen abhängig vom Dateityp zurückzugeben, den wir dekodieren müssen (in diesem Fall wählen wir zwischen Blob und Text, aber das Beispiel kann leicht erweitert werden, um mit anderen Typen zu arbeiten).
  • -, «return» fetch(). , (.. , blob() text(), , , ). , return .

4. Am Ende rufen wir die .catch () -Methode auf, um alle Fehler zu behandeln, die in Versprechungen in dem an .all () übergebenen Array auftreten können. Wenn eines der Versprechen abgelehnt wird, teilt uns der Fangblock mit, mit welchem ​​Versprechen das Problem verbunden war. Der Block .all () wird weiterhin ausgeführt, zeigt jedoch nicht die Ergebnisse an, für die Fehler aufgetreten sind. Wenn .all () abgelehnt werden soll, fügen Sie am Ende einen .catch () - Block hinzu.

Der Code in der Funktion ist asynchron und basiert auf Versprechungen, sodass die gesamte Funktion wie ein Versprechen funktioniert.

4. Als nächstes rufen wir unsere Funktion dreimal auf, um den Prozess des Empfangens und Dekodierens von Bildern und Text zu starten und jedes der Versprechen in eine Variable zu setzen. Fügen Sie Folgendes hinzu:

let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')

5. Als nächstes deklarieren wir einen Promise.all () -Block, um Code erst auszuführen, nachdem alle drei Versprechen erfüllt wurden. Fügen Sie einen Block mit einer leeren Executor-Funktion in .then () hinzu:

Promise.all([coffee, tea, description]).then(values => {

})

Sie können sehen, dass eine Reihe von Versprechungen als Parameter verwendet werden. Der Auftragnehmer startet erst, wenn alle drei Versprechen erfüllt sind. In diesem Fall wird das Ergebnis jedes Versprechens (der dekodierte Antwortkörper) in ein Array eingefügt. Dies kann als [Kaffee-Ergebnisse, Tee-Ergebnisse, Beschreibungsergebnisse] dargestellt werden.

6. Fügen Sie abschließend dem Executor Folgendes hinzu (hier verwenden wir einen ziemlich einfachen synchronen Code, um die Ergebnisse in Variablen einzufügen (URL-Objekte aus Blob erstellen) und Bilder und Text auf der Seite anzuzeigen):

console.log(values)
//       
let objectURL1 = URL.createObjectURL(values[0])
let objectURL2 = URL.createObjectURL(values[1])
let descText = values[2]

//  
let image1 = document.createElement('img')
let image2 = document.createElement('img')
image1.src = objectURL1
image2.src = objectURL2
document.body.appendChild(image1)
document.body.appendChild(image2)

//  
let para = document.createElement('p')
para.textContent = descText
document.body.appendChild(para)

Speichern Sie die Änderungen und laden Sie die Seite neu. Sie sollten sehen, dass alle Komponenten der Benutzeroberfläche geladen werden, obwohl dies nicht sehr attraktiv aussieht.

Hinweis. Wenn Sie irgendwelche Schwierigkeiten haben, können Sie Ihre Version des Codes mit unserer vergleichen - hier ist eine Live-Demo und ein Quellcode .

Hinweis. Um diesen Code zu verbessern, können Sie die angezeigten Elemente durchlaufen, sie empfangen und dekodieren und dann die Ergebnisse in Promise.all () durchlaufen. Dabei werden verschiedene Funktionen gestartet, um jedes Ergebnis je nach Typ anzuzeigen. Auf diese Weise können Sie mit einer beliebigen Anzahl von Elementen arbeiten.

Darüber hinaus können Sie den Typ der resultierenden Datei bestimmen, ohne die Eigenschaft type explizit angeben zu müssen. Dies kann zum Beispiel mit erfolgenresponse.headers.get ('Inhaltstyp') zum Überprüfen des HTTP-Protokoll- Inhaltstyp- Headers .

Führen Sie den Code aus, nachdem Sie das Versprechen erfüllt / abgelehnt haben


Oft müssen Sie Code ausführen, nachdem ein Versprechen abgeschlossen wurde, unabhängig davon, ob es ausgeführt oder abgelehnt wurde. Zuvor mussten wir denselben Code sowohl im Block .then () als auch im Block .catch () einfügen, zum Beispiel:

myPromise
.then(response => {
    doSomething(response)
    runFinalCode()
})
.catch(e => {
    returnError(e)
    runFinalCode()
})

In modernen Browsern steht die Methode .finally () zur Verfügung, mit der Sie den Code nach Abschluss des Versprechens ausführen können. Dadurch werden Wiederholungen vermieden und der Code eleganter. Der Code aus dem vorherigen Beispiel kann wie folgt umgeschrieben werden:

myPromise
.then(response => {
    doSomething(response)
})
.catch(e => {
    returnError(e)
})
.finally(() => {
    runFinalCode()
})

Sie können die Verwendung dieses Ansatzes anhand eines realen Beispiels sehen - der Live-Demo Versprechen-finally.html ( Quellcode ). Es funktioniert genauso wie Promise.all () aus dem vorherigen Beispiel, außer dass wir finally () am Ende der Kette in der Funktion fetchAndDecode () hinzufügen:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log(`There has been a problem with your fetch operation for resource "${url}": ${e.message}`)
    }).finally(() => {
        console.log(`fetch attempt for "${url}" finished.`)
    })
}

Wir erhalten eine Nachricht über den Abschluss jedes Versuchs, die Datei abzurufen.

Hinweis. then () / catch () / finally () ist das asynchrone Äquivalent von synchronem try () / catch () / finally ().

Schreiben Sie Ihr eigenes Versprechen


Die gute Nachricht ist, dass Sie bereits wissen, wie es geht. Wenn Sie mehrere Versprechen mit .then () kombinieren oder kombinieren, um bestimmte Funktionen bereitzustellen, erstellen Sie Ihre eigenen asynchronen und auf Versprechen basierenden Funktionen. Nehmen Sie zum Beispiel unsere Funktion fetchAndDecode () aus dem vorherigen Beispiel.

Die Kombination verschiedener vielversprechungsbasierter APIs zur Erstellung spezifischer Funktionen ist die häufigste Methode, um mit Versprechungen zu arbeiten. Dies zeigt die Flexibilität und Leistungsfähigkeit moderner APIs. Es gibt jedoch einen anderen Weg.

Konstruktor Versprechen ()


Mit dem Konstruktor Promise () können Sie Ihr eigenes Versprechen erstellen. Dies kann in einer Situation erforderlich sein, in der Sie Code aus der alten asynchronen API haben, den Sie "überdimensionieren" müssen. Dies geschieht, um sowohl mit vorhandenem, altem Code, Bibliotheken oder „Frameworks“ als auch mit neuem versprechungsbasiertem Code gleichzeitig arbeiten zu können.

Schauen wir uns ein einfaches Beispiel an - hier verpacken wir den Aufruf von setTimeout () in ein Versprechen - dies startet die Funktion in 2 Sekunden, die das Versprechen (unter Verwendung des übergebenen Aufrufs von resolve ()) mit der Zeichenfolge "Success!" Erfüllt.

let timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('Success!')
    }, 2000)
})

Resolution () und Reject () sind Funktionen, die aufgerufen werden, um ein neues Versprechen zu erfüllen oder abzulehnen. In diesem Fall wird das Versprechen mit der Zeichenfolge „Erfolg!“ Erfüllt.

Wenn Sie dieses Versprechen aufrufen, können Sie den Block .then () hinzufügen, um weiter mit der Zeile „Success!“ Zu arbeiten. So können wir eine Zeile in der Nachricht ausgeben:

timeoutPromise
.then((message) => {
    alert(message)
})

... oder so:

timeoutPromise.then(alert)

Sehen Sie sich eine Live-Demo ( Quellcode ) an.

Das angegebene Beispiel ist nicht sehr flexibel - das Versprechen kann nur mit einer einfachen Zeile erfüllt werden, wir haben keinen Fehlerbehandler - reverse () (tatsächlich benötigt setTimeout () keinen Fehlerbehandler, daher spielt es in diesem Fall keine Rolle).

Hinweis. Warum auflösen () anstatt erfüllen ()? Im Moment lautet die Antwort: Es ist schwer zu erklären.

Wir arbeiten mit der Ablehnung eines Versprechens


Wir können ein abgelehntes Versprechen mit der Reject () -Methode erstellen - genau wie "resolve ()", "Reject ()" einen einfachen Wert annimmt, ist der einfache Wert jedoch nicht das Ergebnis, sondern der Grund für die Ablehnung, d. H. Ein Fehler, der an den Block .catch () übergeben wird.

Erweitern wir das vorherige Beispiel, indem wir eine Bedingung für die Ablehnung eines Versprechens sowie die Möglichkeit hinzufügen, andere Nachrichten als eine Erfolgsnachricht zu empfangen.

Nehmen Sie das vorherige Beispiel und schreiben Sie es folgendermaßen um:

function timeoutPromise(message, interval){
    return new Promise((resolve, reject) => {
        if(message === '' || typeof message !== 'string'){
            reject('Message is empty or not a string')
        } else if(interval < 0 || typeof interval !== number){
            reject('Interval is negative or not a number')
        } else{
            setTimeout(function(){
                resolve(message)
            }, interval)
        }
    })
}

Hier übergeben wir zwei Argumente an die Funktion - Nachricht und Intervall (Zeitverzögerung). In einer Funktion geben wir ein Promise-Objekt zurück.

Im Promise-Konstruktor führen wir mehrere Überprüfungen mit if ... else-Strukturen durch:

  1. Zuerst überprüfen wir die Nachricht. Wenn es leer ist oder keine Zeichenfolge ist, lehnen wir das Versprechen ab und melden einen Fehler.
  2. Zweitens überprüfen wir die Zeitverzögerung. Wenn es eine negative oder keine Zahl ist, lehnen wir das Versprechen ebenfalls ab und melden einen Fehler.
  3. Wenn beide Argumente in Ordnung sind, zeigen Sie die Nachricht nach einer bestimmten Zeit (Intervall) mit setTimeout () an.

Da timeoutPromise () ein Versprechen zurückgibt, können wir .then (), .catch () usw. hinzufügen, um die Funktionalität zu verbessern. Lass uns das tun:

timeoutPromise('Hello there!', 1000)
.then(message => {
    alert(message)
}).catch(e => {
    console.log('Error: ' + e)
})

Nachdem Sie die Änderungen gespeichert und den Code ausgeführt haben, wird nach einer Sekunde eine Meldung angezeigt. Versuchen Sie nun, eine leere Zeichenfolge als Nachricht oder eine negative Zahl als Intervall zu übergeben. Sie sehen eine Fehlermeldung, wenn Sie das Versprechen ablehnen.

Hinweis. Sie finden unsere Version dieses Beispiels auf GitHub - custom-versprechen2.html ( Quelle ).

Realistischeres Beispiel.


Das von uns untersuchte Beispiel erleichtert das Verständnis des Konzepts, ist jedoch nicht wirklich asynchron. Die Asynchronität im Beispiel wird mit setTimeout () simuliert, obwohl sie immer noch die Nützlichkeit von Versprechungen für die Erstellung von Funktionen mit einem angemessenen Workflow, einer guten Fehlerbehandlung usw. zeigt.

Ein Beispiel für eine nützliche asynchrone Anwendung, die den Promise () -Konstruktor verwendet, ist die IDB-Bibliothek (IndexedDB mit Benutzerfreundlichkeit).Jake Archibald. In dieser Bibliothek können Sie die IndexedDB-API (basierend auf den API-Rückruffunktionen zum Speichern und Abrufen von Daten auf der Clientseite) verwenden, die im alten Stil mit Versprechungen geschrieben wurde. Wenn Sie sich die Hauptbibliotheksdatei ansehen, werden Sie feststellen, dass sie dieselben Techniken verwendet, die wir oben untersucht haben. Der folgende Code konvertiert das von vielen IndexedDB-Methoden verwendete grundlegende Abfragemodell in ein vielversprechendes kompatibles Modell:

function promisifyRequest(request){
    return new Promise(function(resolve, reject){
        request.onsuccess = function(){
            resolve(request.result)
        }

        request.onerror = function(){
            reject(request.error)
        }
    })
}

Dies fügt einige Ereignishandler hinzu, die das Versprechen gegebenenfalls erfüllen oder ablehnen:

  • Wenn die Anforderung erfolgreich ist , erfüllt der Onsuccess-Handler das Versprechen mit dem Ergebnis der Anforderung.
  • Wenn die Anforderung fehlschlägt , lehnt der Onerror-Handler das Versprechen mit dem Fehler der Anforderung ab.

Fazit


Versprechen sind eine großartige Möglichkeit, asynchrone Anwendungen zu erstellen, wenn wir nicht wissen, welchen Wert die Funktion zurückgibt oder wie lange es dauern wird. Sie ermöglichen es Ihnen, mit einer Folge von asynchronen Operationen zu arbeiten, ohne tief verschachtelte Rückruffunktionen zu verwenden, und sie unterstützen den Fehlerbehandlungsstil der synchronen try ... catch-Anweisung.

Versprechen werden von allen modernen Browsern unterstützt. Die einzige Ausnahme bilden Opera Mini und IE11 sowie frühere Versionen.

In diesem Artikel haben wir bei weitem nicht alle Merkmale von Versprechungen berücksichtigt, sondern nur die interessantesten und nützlichsten. Sie werden andere Funktionen und Techniken kennenlernen, wenn Sie mehr über Versprechen erfahren möchten.

Die meisten modernen Web-APIs basieren auf Versprechungen, daher müssen Versprechungen bekannt sein. Unter diesen Web-APIs können wir WebRTC , Web-Audio-API , Media Capture und Streams usw. nennen. Versprechen werden immer beliebter, daher ist ihr Studium und Verständnis ein wichtiger Schritt zur Beherrschung des modernen JavaScript.

Siehe auch „Allgemeine JavaScript-Versprechen, die jeder kennen sollte .

Vielen Dank für Ihre Aufmerksamkeit.

Konstruktive Kritik ist willkommen.

All Articles