„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Beherrschen der Versprechensaufhebung in JavaScript

Beherrschen der Versprechensaufhebung in JavaScript

Veröffentlicht am 02.11.2024
Durchsuche:401

Geschrieben von Rosario De Chiara✏️

In JavaScript sind Promises ein leistungsstarkes Tool zur Verarbeitung asynchroner Vorgänge, besonders nützlich bei UI-bezogenen Ereignissen. Sie stellen einen Wert dar, der möglicherweise nicht sofort verfügbar ist, aber irgendwann in der Zukunft behoben wird.

Versprechen ermöglichen (oder sollten es) Entwicklern, saubereren, besser verwaltbaren Code zu schreiben, wenn sie Aufgaben wie API-Aufrufe, Benutzerinteraktionen oder Animationen erledigen. Durch die Verwendung von Methoden wie .then(), .catch() und .finally() ermöglichen Promises eine intuitivere Möglichkeit, mit Erfolgs- und Fehlerszenarien umzugehen und die berüchtigte „Callback-Hölle“ zu vermeiden.

In diesem Artikel verwenden wir die neue Promise.withResolvers()-Methode (März 2024), mit der Sie saubereren und einfacheren Code schreiben können, indem Sie ein Objekt zurückgeben, das drei Dinge enthält: ein neues Promise und zwei Funktionen, eine zum Auflösen des Promise und die andere, um es abzulehnen. Da es sich um ein aktuelles Update handelt, benötigen Sie eine aktuelle Node-Laufzeitumgebung (v>22), um die Beispiele in diesem Artikel auszuführen.

Vergleich der alten und neuen JavaScript-Promise-Methoden

In den beiden folgenden funktional äquivalenten Codeabschnitten können wir den alten Ansatz und den neuen Ansatz vergleichen, der Methode zuzuweisen, ein Versprechen entweder aufzulösen oder abzulehnen:

let resolve, reject;

const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

Math.random() > 0.5 ? resolve("ok") : reject("not ok");

Im obigen Code können Sie die traditionellste Verwendung eines Versprechens sehen: Sie instanziieren ein neues Versprechenobjekt und müssen dann im Konstruktor die beiden Funktionen „Auflösen“ und „Ablehnen“ zuweisen, die wann aufgerufen werden benötigt.

Im folgenden Codeausschnitt wurde derselbe Codeabschnitt mit der neuen Promise.withResolvers()-Methode neu geschrieben und sieht einfacher aus:

const { promise, resolve, reject } = Promise.withResolvers();

Math.random() > 0.5 ? resolve("ok") : reject("not ok");

Hier können Sie sehen, wie der neue Ansatz funktioniert. Es gibt das Promise zurück, auf dem Sie die Methode .then() und die beiden Funktionen „resolve“ und „reject“ aufrufen können.

Der traditionelle Ansatz für Versprechen kapselt die Erstellungs- und Ereignisverarbeitungslogik in einer einzigen Funktion, was einschränkend sein kann, wenn mehrere Bedingungen oder verschiedene Teile des Codes das Versprechen auflösen oder ablehnen müssen.

Im Gegensatz dazu bietet Promise.withResolvers() eine größere Flexibilität, indem es die Erstellung des Promises von der Auflösungslogik trennt, wodurch es für die Verwaltung komplexer Bedingungen oder mehrerer Ereignisse geeignet ist. Für einfache Anwendungsfälle kann die traditionelle Methode jedoch einfacher und vertrauter für diejenigen sein, die mit Standard-Versprechensmustern vertraut sind.

Beispiel aus der Praxis: Aufrufen einer API

Wir können den neuen Ansatz nun an einem realistischeren Beispiel testen. Im folgenden Code sehen Sie ein einfaches Beispiel für einen API-Aufruf:

function fetchData(url) {
    return new Promise((resolve, reject) => {
        fetch(url)
            .then(response => {
                // Check if the response is okay (status 200-299)
                if (response.ok) {
                    return response.json(); // Parse JSON if response is okay
                } else {
                    // Reject the promise if the response is not okay
                    reject(new Error('API Invocation failed'));
                }
            })
            .then(data => {
                // Resolve the promise with the data
                resolve(data);
            })
            .catch(error => {
                // Catch and reject the promise if there is a network error
                reject(error);
            });
    });
}

// Example usage
const apiURL = '';

fetchData(apiURL)
    .then(data => {
        // Handle the resolved data
        console.log('Data received:', data);
    })
    .catch(error => {
        // Handle any errors that occurred
        console.error('Error occurred:', error);
    });

Die fetchData-Funktion ist so konzipiert, dass sie eine URL annimmt und ein Promise zurückgibt, das einen API-Aufruf mithilfe der Fetch-API verarbeitet. Es verarbeitet die Antwort, indem es prüft, ob der Antwortstatus im Bereich von 200–299 liegt, was auf einen Erfolg hinweist.

Bei Erfolg wird die Antwort als JSON geparst und das Promise mit den resultierenden Daten aufgelöst. Wenn die Antwort nicht erfolgreich ist, wird das Promise mit einer entsprechenden Fehlermeldung abgelehnt. Darüber hinaus umfasst die Funktion eine Fehlerbehandlung, um Netzwerkfehler abzufangen und das Promise abzulehnen, wenn ein solcher Fehler auftritt.

Das Beispiel zeigt, wie diese Funktion verwendet wird. Es zeigt, wie die aufgelösten Daten mit einem .then()-Block verwaltet und Fehler mithilfe eines .catch()-Blocks behandelt werden, um sicherzustellen, dass sowohl ein erfolgreicher Datenabruf als auch Fehler ordnungsgemäß verwaltet werden.

Im folgenden Code schreiben wir die Funktion fetchData() neu, indem wir die neue Methode Promise.withResolvers() verwenden:

function fetchData(url) {
    const { promise, resolve, reject } = Promise.withResolvers();

    fetch(url)
        .then(response => {
            // Check if the response is okay (status 200-299)
            if (response.ok) {
                return response.json(); // Parse JSON if response is okay
            } else {
                // Reject the promise if the response is not okay
                reject(new Error('API Invocation failed'));
            }
        })
        .then(data => {
            // Resolve the promise with the data
            resolve(data);
        })
        .catch(error => {
            // Catch and reject the promise if there is a network error
            reject(error);
        });

    return promise;
}

Wie Sie sehen können, ist der obige Code besser lesbar und die Rolle des Objekts Promise ist klar: Die fetchData-Funktion gibt ein Promise zurück, das erfolgreich aufgelöst wird oder fehlschlägt, und ruft in jedem Fall die richtige Methode auf . Sie finden den Code oben im Repository mit dem Namen api.invocation.{old|new}.js.

Verspricht Stornierung

Der folgende Code untersucht, wie eine Promise-Stornierungsmethode implementiert wird. Wie Sie vielleicht wissen, können Sie ein Versprechen in JavaScript nicht stornieren. Versprechen stellen das Ergebnis eines asynchronen Vorgangs dar und sind so konzipiert, dass sie nach ihrer Erstellung aufgelöst oder abgelehnt werden, ohne dass ein integrierter Mechanismus zum Abbrechen vorhanden ist.

Diese Einschränkung entsteht, weil Promises einen definierten Zustandsübergangsprozess haben; Sie beginnen mit dem Status „Ausstehend“ und können, sobald sie erledigt sind, ihren Status nicht mehr ändern. Sie sollen das Ergebnis einer Operation kapseln und nicht die Operation selbst steuern, was bedeutet, dass sie den zugrunde liegenden Prozess nicht beeinflussen oder abbrechen können. Diese Designwahl hält Promises einfach und konzentriert sich auf die Darstellung des letztendlichen Ergebnisses einer Operation:

const cancellablePromise = () => {
    const { promise, resolve, reject } = Promise.withResolvers();

    promise.cancel = () => {
        reject("the promise got cancelled");
    };
    return promise;
};

Im obigen Code können Sie das Objekt namens cancelllablePromise sehen, bei dem es sich um ein Versprechen mit einer zusätzlichen Methode cancel() handelt, die, wie Sie sehen können, einfach den Aufruf der Methode „reject“ erzwingt. Dies ist nur syntaktischer Zucker und hebt ein JavaScript-Versprechen nicht auf, obwohl es dabei helfen kann, klareren Code zu schreiben.

Ein alternativer Ansatz besteht darin, einen AbortController und ein AbortSignal zu verwenden, die an den zugrunde liegenden Vorgang (z. B. eine HTTP-Anfrage) gebunden werden können, um ihn bei Bedarf abzubrechen. Aus der Dokumentation können Sie ersehen, dass der Ansatz „AbortController“ und „AbortSignal“ eine aussagekräftigere Implementierung dessen ist, was wir im obigen Code implementiert haben: Sobald „AbortSignal“ aufgerufen wird, wird das Versprechen einfach abgelehnt.

Ein anderer Ansatz besteht darin, reaktive Programmierbibliotheken wie RxJS zu verwenden, die eine Implementierung des Observable-Musters bieten, eine ausgefeiltere Kontrolle über asynchrone Datenströme, einschließlich Löschfunktionen.

Ein Vergleich zwischen Observablen und Versprechen

Wenn es um praktische Anwendungsfälle geht, eignen sich Promises gut für die Abwicklung einzelner asynchroner Vorgänge, wie zum Beispiel das Abrufen von Daten von einer API. Im Gegensatz dazu eignen sich Observables ideal für die Verwaltung von Datenströmen wie Benutzereingaben, WebSocket-Ereignissen oder HTTP-Antworten, bei denen im Laufe der Zeit mehrere Werte ausgegeben werden können.

Wir haben bereits klargestellt, dass Promises nach ihrer Einführung nicht mehr storniert werden können, während Observables eine Stornierung durch Abmelden vom Stream ermöglichen. Die allgemeine Idee ist, dass Sie mit Observables eine explizite Struktur der möglichen Interaktion mit dem Objekt haben:

  • Sie erstellen ein Observable und dann können alle Observables es abonnieren
  • Das Observable führt seine Arbeit aus, indem es seinen Zustand ändert und Ereignisse aussendet. Alle Beobachter erhalten die Updates – das ist der Hauptunterschied zu Promises. Ein Versprechen kann nur einmal gelöst werden, während die Observablen weiterhin Ereignisse aussenden können, solange es Beobachter gibt
  • Sobald der Beobachter nicht an den Ereignissen der Observables interessiert ist, kann er sich abmelden und so Ressourcen freigeben

Dies wird im folgenden Code demonstriert:

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

const observer = observable.subscribe({
  next(x) { console.log('Received value:', x); },
  complete() { console.log('Observable completed'); }
});

observer.unsubscribe();

Dieser Code kann nicht mit Promises umgeschrieben werden, da das Observable drei Werte zurückgibt, während ein Promise nur einmal aufgelöst werden kann.

Um weiter mit der Unsubscribe-Methode zu experimentieren, können wir einen weiteren Observer hinzufügen, der die takeWhile()-Methode verwendet: Sie lässt den Observer darauf warten, dass Werte einer bestimmten Bedingung entsprechen; Im folgenden Code werden beispielsweise weiterhin Ereignisse vom Observable empfangen, auch wenn der Wert nicht 2 ist:

import { Observable, takeWhile } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

const observer1 = observable.subscribe({
  next(x) { console.log('Received by 1 value:', x); },
  complete() { console.log('Observable 1 completed'); }
});

const observer2 = observable.pipe(
  takeWhile(value => value != "2")
).subscribe(value => console.log('Received by 2 value:', value));

Im obigen Code ist Observer1 dasselbe, was wir bereits gesehen haben: Er abonniert einfach und empfängt weiterhin alle Ereignisse vom Observable. Der zweite, Observer2, empfängt Elemente vom Observable, solange die Bedingung erfüllt ist. In diesem Fall bedeutet dies, dass der Wert ungleich 2 ist.

Anhand der Ausführung können Sie sehen, wie die beiden unterschiedlichen Mechanismen funktionieren:

$ node observable.mjs
Received by 1 value: 1
Received by 1 value: 2
Received by 1 value: 3
Observable 1 completed
Received by 2 value: 1
$

Abschluss

In diesem Artikel haben wir den neuen Mechanismus zum Zuweisen eines Promises in JavaScript untersucht und einige der möglichen Möglichkeiten dargelegt, ein Promise vor seiner Fertigstellung abzubrechen. Wir haben Promises auch mit beobachtbaren Objekten verglichen, die nicht nur die Funktionen von Promises bieten, sondern diese erweitern, indem sie mehrere Emissionen von Ereignissen und einen geeigneten Mechanismus zum Abbestellen ermöglichen.


LogRocket: JavaScript-Fehler einfacher beheben, indem Sie den Kontext verstehen

Das Debuggen von Code ist immer eine mühsame Aufgabe. Aber je besser Sie Ihre Fehler verstehen, desto einfacher ist es, sie zu beheben.

LogRocket ermöglicht es Ihnen, diese Fehler auf neue und einzigartige Weise zu verstehen. Unsere Frontend-Überwachungslösung verfolgt die Benutzerinteraktion mit Ihren JavaScript-Frontends, um Ihnen die Möglichkeit zu geben, genau zu sehen, was der Benutzer getan hat, was zu einem Fehler geführt hat.

Mastering promise cancellation in JavaScript

LogRocket zeichnet Konsolenprotokolle, Seitenladezeiten, Stack-Traces, langsame Netzwerkanforderungen/-antworten mit Header-Körpern, Browser-Metadaten und benutzerdefinierten Protokollen auf. Es wird nie einfacher sein, die Auswirkungen Ihres JavaScript-Codes zu verstehen!

Probieren Sie es kostenlos aus.

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/logrocket/mastering-promise-cancellation-in-javascript-1eb7?1 Bei Verstößen wenden Sie sich bitte an [email protected], um ihn zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3