JavaScript-Versprechen verstehen

Einführung

JavaScript Promises können schwierig zu verstehen sein. Daher möchte ich die Art und Weise, wie ich Promises verstehe, niederschreiben.

Promises verstehen

Ein Promise kurz gesagt:

“Stell dir vor, du bist ein Kind. Deine Mutter verspricht dir, dass sie dir nächste Woche ein neues Handy kauft.”

Du weißt nicht ob du das Handy bekommst, bis nächste Woche. Deine Mutter kann dir wirklich ein brandneues Handy kaufen, oder sie tut es nicht.

Das ist ein Versprechen. Ein Versprechen hat drei Zustände. Sie sind:

  1. Pending: Du weißt nicht ob du das Handy bekommst
  2. Fulfilled: Mutter ist zufrieden, sie kauft dir ein brandneues Handy
  3. Rejected: Mutter ist unzufrieden, sie kauft dir kein Handy

Ein Promise erstellen

Lassen Sie uns dies in JavaScript umwandeln.

// ES5: Teil 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // erfüllt
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // ablehnen
        }

    }
);

Der Code ist selbst recht ausdrucksstark.

Unten sehen Sie, wie die Promise-Syntax normalerweise aussieht:

// Promise-Syntax sieht so aus
new Promise(function (resolve, reject) { ... } );

Promise konsumieren

Nachdem wir die Promise haben, lassen Sie uns sie konsumieren:

// ES5: Teil 2

var willIGetNewPhone = ... // fortsetzen von Teil 1

// unsere Promise aufrufen
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, du hast ein neues Handy bekommen
            console.log(fulfilled);
             // Ausgabe: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // oops, Mama hat es nicht gekauft
            console.log(error.message);
             // Ausgabe: 'Mama ist nicht glücklich'
        });
};

askMom();

Lassen Sie uns das Beispiel ausführen und das Ergebnis sehen!

Demo: https://jsbin.com/nifocu/1/edit?js,console

Ketten von Promises

Promises sind verkettbar.

Stell dir vor, du, das Kind, versprichst deinem Freund, dass du ihm das neue Telefon zeigst, wenn deine Mutter dir eines kauft.

Das ist ein weiteres Versprechen. Lass es uns aufschreiben!

// ES5

// 2. Versprechen
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

Hinweis: Wir können den obigen Code verkürzen, indem wir ihn wie folgt schreiben:

// verkürzen

// 2. Versprechen
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

Lass uns die Promises verketten. Du, das Kind, kannst das showOff Versprechen nur starten, nachdem das willIGetNewPhone Versprechen erfüllt wurde.

// Versprechen aufrufen
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // hier verketten
    .then(function (fulfilled) {
            console.log(fulfilled);
         // Ausgabe: 'Hey Freund, ich habe ein neues schwarzes Samsung-Telefon.'
        })
        .catch(function (error) {
            // Hoppla, die Mutter kauft es nicht
            console.log(error.message);
         // Ausgabe: 'Mama ist nicht glücklich'
        });
};

So kannst du das Versprechen verketten.

Versprechen sind asynchron

Versprechen sind asynchron. Lassen Sie uns eine Nachricht vor und nach dem Aufruf des Versprechens protokollieren.

// rufen wir unser Versprechen auf
var askMom = function () {
    console.log('before asking Mom'); // log vorher
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // log nachher
}

Welche Ausgabereihenfolge erwarten Sie? Sie könnten erwarten:

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

Tatsächlich ist die Ausgabereihenfolge jedoch:

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

Sie würden nicht aufhören zu spielen, während Sie auf das Versprechen Ihrer Mutter (das neue Telefon) warten. Das nennen wir asynchron: Der Code wird ausgeführt, ohne zu blockieren oder auf das Ergebnis zu warten. Alles, was auf das Ergebnis eines Versprechens warten muss, wird in .then eingefügt.

Hier ist das vollständige Beispiel in ES5:

// ES5: Vollständiges Beispiel

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // erfüllt
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // ablehnen
        }

    }
);

// 2. Promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// unsere Promise aufrufen
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // hier verketten
    .then(function (fulfilled) {
            console.log(fulfilled);
            // Ausgabe: 'Hallo Freund, ich habe ein neues schwarzes Samsung-Handy.'
        })
        .catch(function (error) {
            // hoppla, Mama kauft es nicht
            console.log(error.message);
            // Ausgabe: 'Mama ist nicht zufrieden'
        });
};

askMom();

Promises in ES5, ES6/2015, ES7/Next

ES5 – Die meisten Browser

Der Democode ist in ES5-Umgebungen (alle wichtigen Browser + NodeJs) funktionstüchtig, wenn Sie die Bluebird-Promise-Bibliothek einbinden. Das liegt daran, dass ES5 Promises nicht out-of-the-box unterstützt. Eine weitere bekannte Promise-Bibliothek ist Q von Kris Kowal.

ES6 / ES2015 – Moderne Browser, NodeJs v6

Die Democode funktioniert direkt, weil ES6 Promises nativ unterstützt. Darüber hinaus können wir mit ES6-Funktionen den Code weiter vereinfachen, indem wir eine Pfeilfunktion verwenden und const und let nutzen.

Hier ist das vollständige Beispiel in ES6-Code:

//_ ES6: Vollständiges Beispiel_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // Pfeilfunktion
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2. Promise
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// rufe unser Promise auf
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // Pfeilfunktion
        .catch(error => console.log(error.message)); // Pfeilfunktion
};

askMom();

Beachte, dass alle var durch const ersetzt wurden. Alle function(resolve, reject) wurden vereinfacht zu (resolve, reject) =>. Diese Änderungen bringen einige Vorteile mit sich.

ES7 – Async/Await

ES7 führte die Syntax async und await ein. Sie macht die asynchrone Syntax leichter verständlich, ohne .then und .catch.

Schreiben wir unser Beispiel mit ES7-Syntax um:

// ES7: Vollständiges Beispiel
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2. Promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// rufe unser Promise im ES7 async await Stil auf
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await auch hier
(async () => {
    await askMom();
})();

Versprechen und wann man sie nutzen sollte

Warum benötigen wir Versprechen? Wie sah die Welt vor den Versprechen aus? Bevor wir diese Fragen beantworten, gehen wir zurück zu den Grundlagen.

Normale Funktion VS Asynchrone Funktion

Schauen wir uns diese beiden Beispiele an. Beide Beispiele führen die Addition von zwei Zahlen durch: eine fügt mit normalen Funktionen hinzu und die andere fügt remote hinzu.

Normale Funktion zur Addition von zwei Zahlen

// zwei Zahlen normal addieren

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // du erhältst sofort result = 3
Asynchrone Funktion zur Addition von zwei Zahlen
// zwei Zahlen remote addieren

// das Ergebnis durch Aufrufen einer API erhalten
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// du erhältst result = "undefined"

Wenn du die Zahlen mit der normalen Funktion addierst, erhältst du das Ergebnis sofort. Wenn du jedoch einen Remote-Aufruf ausführst, um das Ergebnis zu erhalten, musst du warten und kannst das Ergebnis nicht sofort erhalten.

Sie wissen nicht, ob Sie das Ergebnis erhalten, da der Server möglicherweise ausgefallen ist, eine langsame Reaktionszeit hat usw. Sie möchten nicht, dass Ihr gesamter Prozess blockiert wird, während Sie auf das Ergebnis warten.

Das Aufrufen von APIs, das Herunterladen von Dateien und das Lesen von Dateien gehören zu den üblichen asynchronen Operationen, die Sie durchführen werden.

Sie müssen keine Promises für einen asynchronen Aufruf verwenden. Vor Promises haben wir Callbacks verwendet. Callbacks sind eine Funktion, die Sie aufrufen, wenn Sie das Rückgabeergebnis erhalten. Lassen Sie uns das vorherige Beispiel so ändern, dass es einen Callback akzeptiert.

// zwei Zahlen remote addieren
// das Ergebnis durch Aufrufen einer API erhalten

function addAsync (num1, num2, callback) {
    // verwende die bekannte jQuery getJSON Callback-API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // Callback
    const result = success; // Sie erhalten hier result = 3
});

Nachfolgende asynchrone Aktion

Anstatt die Zahlen nacheinander zu addieren, möchten wir dreimal addieren. In einer normalen Funktion würden wir dies tun:

// zwei Zahlen normal addieren

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // Sie erhalten sofort resultA = 3
resultB = add(resultA, 3); // Sie erhalten sofort resultB = 6
resultC = add(resultB, 4); // Sie erhalten sofort resultC = 10

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

So sieht das mit Callbacks aus:

// zwei Zahlen remote addieren
// das Ergebnis über einen API-Aufruf erhalten

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // die bekannte jQuery getJSON Callback API verwenden
	// https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // Callback 1
    resultA = success; // hier erhältst du result = 3

    addAsync(resultA, 3, success => {
        // Callback 2
        resultB = success; // hier erhältst du result = 6

        addAsync(resultB, 4, success => {
            // Callback 3
            resultC = success; // hier erhältst du result = 10

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

Demo: https://jsbin.com/barimo/edit?html,js,console

Diese Syntax ist aufgrund der tief verschachtelten Callbacks weniger benutzerfreundlich.

Tief verschachtelte Callbacks vermeiden

Promises können dir dabei helfen, tief verschachtelte Callbacks zu vermeiden. Schauen wir uns die Promise-Version desselben Beispiels an:

// zwei Zahlen remote mit Observables addieren

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // ES6 fetch API verwenden, die ein Promise zurückgibt
	// Was ist .json()? https://developer.mozilla.org/de/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

Mit Promises glätten wir den Callback mit .then. Es sieht auf eine Weise sauberer aus, weil keine Callback-Verschachtelung vorhanden ist. Mit der ES7 async-Syntax könnten Sie dieses Beispiel weiter verbessern.

Observables

Bevor Sie sich für Promises entscheiden, gibt es etwas, das aufgetaucht ist, um Sie bei der Handhabung von asynchronen Daten zu unterstützen, nämlich Observables.

Schauen wir uns das gleiche Beispiel an, das mit Observables geschrieben wurde. In diesem Beispiel verwenden wir RxJS für die Observables.

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // ES6 fetch API verwenden, die ein Promise zurückgibt
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Observables können noch interessantere Dinge tun. Zum Beispiel kann die delay-Funktion um 3 Sekunden verzögern, und das mit nur einer Codezeile, oder wiederholen, so dass Sie einen Aufruf eine bestimmte Anzahl von Malen wiederholen können.

...

addAsync(1,2)
  .delay(3000) // 3 Sekunden Verzögerung
  .do(x => resultA = x)
  ...

Sie können eins meiner RxJs-Beiträge hier lesen.

Schlussfolgerung

Das Kennenlernen von Callbacks und Promises ist wichtig. Verstehen und verwenden Sie sie. Machen Sie sich noch keine Sorgen über Observables. Je nach Situation können alle drei in Ihrer Entwicklung eine Rolle spielen.

Source:
https://www.digitalocean.com/community/tutorials/understanding-javascript-promises