25. Promises für asynchrone Programmierung
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

25. Promises für asynchrone Programmierung

Dieses Kapitel ist eine Einführung in die asynchrone Programmierung mithilfe von Promises im Allgemeinen und der ECMAScript 6 Promise API im Besonderen. Das vorherige Kapitel erklärt die Grundlagen der asynchronen Programmierung in JavaScript. Sie können es konsultieren, wann immer es etwas gibt, das Sie in diesem Kapitel nicht verstehen.



25.1 Übersicht

Promises sind eine Alternative zu Callbacks für die Bereitstellung der Ergebnisse einer asynchronen Berechnung. Sie erfordern mehr Aufwand von den Implementierern asynchroner Funktionen, bieten aber mehrere Vorteile für die Benutzer dieser Funktionen.

Die folgende Funktion gibt ein Ergebnis asynchron über ein Promise zurück

function asyncFunc() {
    return new Promise(
        function (resolve, reject) {
            ···
            resolve(result);
            ···
            reject(error);
        });
}

Sie rufen asyncFunc() wie folgt auf

asyncFunc()
.then(result => { ··· })
.catch(error => { ··· });

25.1.1 Verketten von then()-Aufrufen

then() gibt immer ein Promise zurück, was Ihnen ermöglicht, Methodenaufrufe zu verketten

asyncFunc1()
.then(result1 => {
    // Use result1
    return asyncFunction2(); // (A)
})
.then(result2 => { // (B)
    // Use result2
})
.catch(error => {
    // Handle errors of asyncFunc1() and asyncFunc2()
});

Wie das Promise P, das von then() zurückgegeben wird, abgewickelt wird, hängt davon ab, was sein Callback tut

Darüber hinaus sehen Sie, wie catch() die Fehler von zwei asynchronen Funktionsaufrufen (asyncFunction1() und asyncFunction2()) behandelt. Das heißt, nicht abgefangene Fehler werden weitergegeben, bis ein Fehlerhandler vorhanden ist.

25.1.2 Ausführen von asynchronen Funktionen parallel

Wenn Sie asynchrone Funktionsaufrufe über then() verketten, werden sie sequenziell, einer nach dem anderen, ausgeführt

asyncFunc1()
.then(() => asyncFunc2());

Wenn Sie das nicht tun und alle sofort aufrufen, werden sie im Grunde parallel ausgeführt (ein Fork in der Unix-Prozess-Terminologie)

asyncFunc1();
asyncFunc2();

Promise.all() ermöglicht es Ihnen, benachrichtigt zu werden, sobald alle Ergebnisse vorliegen (ein Join in der Unix-Prozess-Terminologie). Seine Eingabe ist ein Array von Promises, seine Ausgabe ist ein einzelnes Promise, das mit einem Array der Ergebnisse erfüllt wird.

Promise.all([
    asyncFunc1(),
    asyncFunc2(),
])
.then(([result1, result2]) => {
    ···
})
.catch(err => {
    // Receives first rejection among the Promises
    ···
});

25.1.3 Glossar: Promises

Die Promise API dient der asynchronen Bereitstellung von Ergebnissen. Ein Promise-Objekt (kurz: Promise) ist ein Platzhalter für das Ergebnis, das über dieses Objekt geliefert wird.

Zustände

Reagieren auf Zustandsänderungen

Zustandsänderung: Es gibt zwei Operationen zur Änderung des Zustands eines Promise. Nachdem Sie eine davon einmal aufgerufen haben, haben weitere Aufrufe keine Auswirkung.

25.2 Einführung: Promises

Promises sind ein Muster, das bei einer bestimmten Art der asynchronen Programmierung hilft: einer Funktion (oder Methode), die ein einzelnes Ergebnis asynchron zurückgibt. Eine beliebte Methode, ein solches Ergebnis zu empfangen, ist über einen Callback („Callbacks als Continuations“)

asyncFunction(arg1, arg2,
    result => {
        console.log(result);
    });

Promises bieten eine bessere Möglichkeit, mit Callbacks zu arbeiten: Nun gibt eine asynchrone Funktion ein Promise zurück, ein Objekt, das als Platzhalter und Container für das Endergebnis dient. Callbacks, die über die Promise-Methode then() registriert werden, werden über das Ergebnis benachrichtigt

asyncFunction(arg1, arg2)
.then(result => {
    console.log(result);
});

Im Vergleich zu Callbacks als Continuations haben Promises die folgenden Vorteile

25.3 Ein erstes Beispiel

Schauen wir uns ein erstes Beispiel an, um Ihnen einen Eindruck davon zu geben, wie das Arbeiten mit Promises ist.

Mit Node.js-Style-Callbacks sieht das asynchrone Lesen einer Datei so aus

fs.readFile('config.json',
    function (error, text) {
        if (error) {
            console.error('Error while reading config file');
        } else {
            try {
                const obj = JSON.parse(text);
                console.log(JSON.stringify(obj, null, 4));
            } catch (e) {
                console.error('Invalid JSON in file');
            }
        }
    });

Mit Promises wird dieselbe Funktionalität so verwendet

readFilePromisified('config.json')
.then(function (text) { // (A)
    const obj = JSON.parse(text);
    console.log(JSON.stringify(obj, null, 4));
})
.catch(function (error) { // (B)
    // File read error or JSON SyntaxError
    console.error('An error occurred', error);
});

Es gibt immer noch Callbacks, aber sie werden über Methoden bereitgestellt, die auf dem Ergebnis aufgerufen werden (then() und catch()). Der Fehler-Callback in Zeile B ist aus zwei Gründen praktisch: Erstens ist es ein einheitlicher Stil der Fehlerbehandlung (im Gegensatz zu if (error) und try-catch im vorherigen Beispiel). Zweitens können Sie die Fehler von readFilePromisified() und dem Callback in Zeile A von einem einzigen Ort aus behandeln.

Der Code von readFilePromisified() ist später gezeigt.

25.4 Drei Arten, Promises zu verstehen

Betrachten wir drei Arten, Promises zu verstehen.

Der folgende Code enthält eine Promise-basierte Funktion asyncFunc() und ihren Aufruf.

function asyncFunc() {
    return new Promise((resolve, reject) => { // (A)
        setTimeout(() => resolve('DONE'), 100); // (B)
    });
}
asyncFunc()
.then(x => console.log('Result: '+x));

// Output:
// Result: DONE

asyncFunc() gibt ein Promise zurück. Sobald das eigentliche Ergebnis 'DONE' der asynchronen Berechnung bereit ist, wird es über resolve() (Zeile B) geliefert, was ein Parameter des Callbacks ist, der in Zeile A beginnt.

Was ist also ein Promise?

25.4.1 Konzeptionell: Der Aufruf einer Promise-basierten Funktion ist blockierend

Der folgende Code ruft asyncFunc() aus der asynchronen Funktion main() auf. Async-Funktionen sind ein Merkmal von ECMAScript 2017.

async function main() {
    const x = await asyncFunc(); // (A)
    console.log('Result: '+x); // (B)

    // Same as:
    // asyncFunc()
    // .then(x => console.log('Result: '+x));
}
main();

Der Körper von main() drückt gut aus, was konzeptionell vor sich geht, wie wir normalerweise über asynchrone Berechnungen denken. Nämlich ist asyncFunc() ein blockierender Funktionsaufruf

Vor ECMAScript 6 und Generatoren konnte man keinen Code unterbrechen und fortsetzen. Deshalb setzt man bei Promises alles, was nach der Fortsetzung des Codes passiert, in einen Callback. Das Aufrufen dieses Callbacks ist gleichbedeutend mit dem Fortsetzen des Codes.

25.4.2 Ein Promise ist ein Container für einen asynchron gelieferten Wert

Wenn eine Funktion ein Promise zurückgibt, ist dieses Promise wie ein Blanko, in das die Funktion (normalerweise) ihr Ergebnis einfüllen wird, sobald sie es berechnet hat. Sie können einen einfachen Teil dieses Prozesses über ein Array simulieren

function asyncFunc() {
    const blank = [];
    setTimeout(() => blank.push('DONE'), 100);
    return blank;
}
const blank = asyncFunc();
// Wait until the value has been filled in
setTimeout(() => {
    const x = blank[0]; // (A)
    console.log('Result: '+x);
}, 200);

Bei Promises greifen Sie nicht über [0] auf den zukünftigen Wert zu (wie in Zeile A), sondern verwenden die Methode then() und einen Callback.

25.4.3 Ein Promise ist ein Ereignis-Emitter

Eine weitere Möglichkeit, ein Promise zu betrachten, ist als Objekt, das Ereignisse emittiert.

function asyncFunc() {
    const eventEmitter = { success: [] };
    setTimeout(() => { // (A)
        for (const handler of eventEmitter.success) {
            handler('DONE');
        }
    }, 100);
    return eventEmitter;
}
asyncFunc()
.success.push(x => console.log('Result: '+x)); // (B)

Die Registrierung des Ereignis-Listeners (Zeile B) kann nach dem Aufruf von asyncFunc() erfolgen, da der an setTimeout() (Zeile A) übergebene Callback asynchron ausgeführt wird (nachdem dieser Codeabschnitt beendet ist).

Normale Ereignis-Emitter sind darauf spezialisiert, mehrere Ereignisse zu liefern, und beginnen, sobald Sie sich registrieren.

Im Gegensatz dazu sind Promises darauf spezialisiert, genau einen Wert zu liefern und verfügen über einen integrierten Schutz gegen zu späte Registrierung: Das Ergebnis eines Promise wird zwischengespeichert und an Ereignis-Listener weitergegeben, die registriert werden, nachdem das Promise abgewickelt wurde.

25.5 Erstellen und Verwenden von Promises

Werfen wir einen Blick darauf, wie Promises auf der Produzenten- und Konsumentenseite operieren.

25.5.1 Produzieren eines Promise

Als Produzent erstellen Sie ein Promise und senden ein Ergebnis darüber

const p = new Promise(
    function (resolve, reject) { // (A)
        ···
        if (···) {
            resolve(value); // success
        } else {
            reject(reason); // failure
        }
    });

25.5.2 Die Zustände von Promises

Sobald ein Ergebnis über ein Promise geliefert wurde, bleibt das Promise an dieses Ergebnis gebunden. Das bedeutet, dass jedes Promise immer in einem von drei (sich gegenseitig ausschließenden) Zuständen ist

Ein Promise ist settled (die von ihm dargestellte Berechnung ist abgeschlossen), wenn es entweder fulfilled oder rejected ist. Ein Promise kann nur einmal settled sein und bleibt dann settled. Nachfolgende Versuche, es zu settle, haben keine Auswirkung.

Der Parameter von new Promise() (beginnend in Zeile A) wird als Executor bezeichnet

Wenn eine Ausnahme innerhalb des Executors ausgelöst wird, wird p mit dieser Ausnahme abgelehnt.

25.5.3 Konsumieren eines Promise

Als Konsument von promise werden Sie über eine Erfüllung oder Ablehnung durch Reaktionen benachrichtigt – Callbacks, die Sie mit den Methoden then() und catch() registrieren

promise
.then(value => { /* fulfillment */ })
.catch(error => { /* rejection */ });

Was Promises für asynchrone Funktionen (mit einmaligen Ergebnissen) so nützlich macht, ist, dass sich ein Promise, sobald es settled ist, nicht mehr ändert. Darüber hinaus gibt es nie Race Conditions, da es keine Rolle spielt, ob Sie then() oder catch() vor oder nach der Abwicklung eines Promise aufrufen

Beachten Sie, dass catch() einfach eine bequemere (und empfohlene) Alternative zum Aufrufen von then() ist. Das heißt, die folgenden beiden Aufrufe sind äquivalent

promise.then(
    null,
    error => { /* rejection */ });

promise.catch(
    error => { /* rejection */ });

25.5.4 Promises sind immer asynchron

Eine Promise-Bibliothek hat die vollständige Kontrolle darüber, ob Ergebnisse synchron (sofort) oder asynchron (nach Abschluss der aktuellen Fortsetzung, des aktuellen Code-Abschnitts) an Promise-Reaktionen geliefert werden. Die Promises/A+-Spezifikation verlangt jedoch, dass immer der letztere Ausführungsmodus verwendet wird. Dies wird durch die folgende Anforderung (2.2.4) für die Methode then() festgelegt

onFulfilled oder onRejected dürfen nicht aufgerufen werden, bis der Ausführungskontextstapel nur noch Plattformcode enthält.

Das bedeutet, dass Ihr Code sich auf Run-to-Completion-Semantik verlassen kann (wie im vorherigen Kapitel erläutert) und dass das Verketten von Promises andere Aufgaben nicht blockiert.

Darüber hinaus verhindert diese Einschränkung, dass Sie Funktionen schreiben, die manchmal Ergebnisse sofort und manchmal asynchron zurückgeben. Dies ist ein Anti-Muster, da es den Code unvorhersehbar macht. Weitere Informationen finden Sie unter „Designing APIs for Asynchrony“ von Isaac Z. Schlueter.

25.6 Beispiele

Bevor wir tiefer in Promises eintauchen, nutzen wir das bisher Gelernte in einigen Beispielen.

25.6.1 Beispiel: Promisifizieren von fs.readFile()

Der folgende Code ist eine Promise-basierte Version der integrierten Node.js-Funktion fs.readFile().

import {readFile} from 'fs';

function readFilePromisified(filename) {
    return new Promise(
        function (resolve, reject) {
            readFile(filename, { encoding: 'utf8' },
                (error, data) => {
                    if (error) {
                        reject(error);
                    } else {
                        resolve(data);
                    }
                });
        });
}

readFilePromisified() wird so verwendet

readFilePromisified(process.argv[2])
.then(text => {
    console.log(text);
})
.catch(error => {
    console.log(error);
});

25.6.2 Beispiel: Promisifizieren von XMLHttpRequest

Die folgende Funktion ist eine Promise-basierte Funktion, die einen HTTP-GET-Request über die ereignisbasierte XMLHttpRequest API durchführt

function httpGet(url) {
    return new Promise(
        function (resolve, reject) {
            const request = new XMLHttpRequest();
            request.onload = function () {
                if (this.status === 200) {
                    // Success
                    resolve(this.response);
                } else {
                    // Something went wrong (404 etc.)
                    reject(new Error(this.statusText));
                }
            };
            request.onerror = function () {
                reject(new Error(
                    'XMLHttpRequest Error: '+this.statusText));
            };
            request.open('GET', url);
            request.send();
        });
}

So verwenden Sie httpGet()

httpGet('http://example.com/file.txt')
.then(
    function (value) {
        console.log('Contents: ' + value);
    },
    function (reason) {
        console.error('Something went wrong', reason);
    });

25.6.3 Beispiel: Verzögern einer Aktivität

Lassen Sie uns setTimeout() als Promise-basierte Funktion delay() implementieren (ähnlich wie bei Q.delay()).

function delay(ms) {
    return new Promise(function (resolve, reject) {
        setTimeout(resolve, ms); // (A)
    });
}

// Using delay():
delay(5000).then(function () { // (B)
    console.log('5 seconds have passed!')
});

Beachten Sie, dass in Zeile A resolve ohne Parameter aufgerufen wird, was dasselbe ist wie resolve(undefined). Wir benötigen auch keinen Erfüllungswert in Zeile B und ignorieren ihn einfach. Allein die Benachrichtigung reicht hier aus.

25.6.4 Beispiel: Zeitüberschreitung eines Promise

function timeout(ms, promise) {
    return new Promise(function (resolve, reject) {
        promise.then(resolve);
        setTimeout(function () {
            reject(new Error('Timeout after '+ms+' ms')); // (A)
        }, ms);
    });
}

Beachten Sie, dass die Ablehnung nach Ablauf der Zeitüberschreitung (in Zeile A) die Anfrage nicht abbricht, aber sie verhindert, dass das Promise mit seinem Ergebnis erfüllt wird.

Die Verwendung von timeout() sieht so aus

timeout(5000, httpGet('http://example.com/file.txt'))
.then(function (value) {
    console.log('Contents: ' + value);
})
.catch(function (reason) {
    console.error('Error or timeout', reason);
});

25.7 Weitere Möglichkeiten, Promises zu erstellen

Nun sind wir bereit, tiefer in die Funktionen von Promises einzutauchen. Lassen Sie uns zunächst zwei weitere Möglichkeiten untersuchen, Promises zu erstellen.

25.7.1 Promise.resolve()

Promise.resolve(x) funktioniert wie folgt

Das bedeutet, dass Sie Promise.resolve() verwenden können, um jeden Wert (Promise, Thenable oder etwas anderes) in ein Promise zu konvertieren. Tatsächlich wird es von Promise.all() und Promise.race() verwendet, um Arrays mit beliebigen Werten in Arrays von Promises zu konvertieren.

25.7.2 Promise.reject()

Promise.reject(err) gibt ein Promise zurück, das mit err abgelehnt wird

const myError = new Error('Problem!');
Promise.reject(myError)
.catch(err => console.log(err === myError)); // true

25.8 Verketten von Promises

In diesem Abschnitt betrachten wir genauer, wie Promises verkettet werden können. Das Ergebnis des Methodenaufrufs

P.then(onFulfilled, onRejected)

ist ein neues Promise Q. Das bedeutet, dass Sie den Promise-basierten Kontrollfluss fortsetzen können, indem Sie then() auf Q aufrufen

25.8.1 Auflösen von Q mit einem normalen Wert

Wenn Sie das von then() zurückgegebene Promise Q mit einem normalen Wert auflösen, können Sie diesen Wert über ein nachfolgendes then() abrufen

asyncFunc()
.then(function (value1) {
    return 123;
})
.then(function (value2) {
    console.log(value2); // 123
});

25.8.2 Auflösen von Q mit einem Thenable

Sie können das von then() zurückgegebene Promise Q auch mit einem Thenable R auflösen. Ein Thenable ist jedes Objekt, das eine then()-Methode hat, die wie Promise.prototype.then() funktioniert. Daher sind Promises Thenables. Das Auflösen mit R (z.B. durch Rückgabe von onFulfilled) bedeutet, dass es „nach“ Q eingefügt wird: R’s Abwicklung wird an Q’s onFulfilled und onRejected Callbacks weitergeleitet. In gewisser Weise wird Q zu R.

Der Hauptzweck dieses Mechanismus ist das Abflachen von verschachtelten then()-Aufrufen, wie im folgenden Beispiel

asyncFunc1()
.then(function (value1) {
    asyncFunc2()
    .then(function (value2) {
        ···
    });
})

Die flache Version sieht so aus

asyncFunc1()
.then(function (value1) {
    return asyncFunc2();
})
.then(function (value2) {
    ···
})

25.8.3 Auflösen von Q aus onRejected

Was auch immer Sie in einem Fehlerhandler zurückgeben, wird zu einem Erfüllungswert (nicht zu einem Ablehnungswert!). Das ermöglicht es Ihnen, Standardwerte anzugeben, die im Fehlerfall verwendet werden

retrieveFileName()
.catch(function () {
    // Something went wrong, use a default value
    return 'Untitled.txt';
})
.then(function (fileName) {
    ···
});

25.8.4 Ablehnen von Q durch Auslösen einer Ausnahme

Ausnahmen, die in den Callbacks von then() und catch() ausgelöst werden, werden als Ablehnungen an den nächsten Fehlerhandler weitergegeben

asyncFunc()
.then(function (value) {
    throw new Error();
})
.catch(function (reason) {
    // Handle error here
});

25.8.5 Verketten und Fehler

Es kann ein oder mehrere then()-Methodenaufrufe geben, die keine Fehlerhandler haben. Dann wird der Fehler weitergereicht, bis ein Fehlerhandler vorhanden ist.

asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.catch(function (reason) {
    // Something went wrong above
});

25.9 Häufige Fehler beim Verketten von Promises

25.9.1 Fehler: Verlieren des Schwanzes einer Promise-Kette

Im folgenden Code wird eine Kette von zwei Promises aufgebaut, aber nur der erste Teil davon wird zurückgegeben. Infolgedessen geht der Schwanz der Kette verloren.

// Don’t do this
function foo() {
    const promise = asyncFunc();
    promise.then(result => {
        ···
    });

    return promise;
}

Dies kann durch Rückgabe des Schwanzes der Kette behoben werden

function foo() {
    const promise = asyncFunc();
    return promise.then(result => {
        ···
    });
}

Wenn Sie die Variable promise nicht benötigen, können Sie diesen Code weiter vereinfachen

function foo() {
    return asyncFunc()
    .then(result => {
        ···
    });
}

25.9.2 Fehler: Verschachteln von Promises

Im folgenden Code ist der Aufruf von asyncFunc2() verschachtelt

// Don’t do this
asyncFunc1()
.then(result1 => {
    asyncFunc2()
    .then(result2 => {
        ···
    });
});

Die Lösung besteht darin, diesen Code zu ent-verschachteln, indem das zweite Promise vom ersten then() zurückgegeben und über ein zweites, verkettetes then() verarbeitet wird

asyncFunc1()
.then(result1 => {
    return asyncFunc2();
})
.then(result2 => {
    ···
});

25.9.3 Fehler: Erstellen von Promises statt Verketten

Im folgenden Code erstellt die Methode insertInto() ein neues Promise für ihr Ergebnis (Zeile A)

// Don’t do this
class Model {
    insertInto(db) {
        return new Promise((resolve, reject) => { // (A)
          db.insert(this.fields) // (B)
          .then(resultCode => {
              this.notifyObservers({event: 'created', model: this});
              resolve(resultCode); // (C)
          }).catch(err => {
              reject(err); // (D)
          })
        });
    }
    ···
}

Wenn Sie genau hinschauen, sehen Sie, dass das Ergebnis-Promise hauptsächlich dazu dient, die Erfüllung (Zeile C) und die Ablehnung (Zeile D) des asynchronen Methodenaufrufs db.insert() (Zeile B) weiterzuleiten.

Die Lösung besteht darin, kein Promise zu erstellen, indem man sich auf then() und Verkettung verlässt

class Model {
    insertInto(db) {
        return db.insert(this.fields) // (A)
        .then(resultCode => {
            this.notifyObservers({event: 'created', model: this});
            return resultCode; // (B)
        });
    }
    ···
}

Erläuterungen

25.9.4 Fehler: Verwenden von then() zur Fehlerbehandlung

Im Prinzip ist catch(cb) eine Abkürzung für then(null, cb). Aber die gleichzeitige Verwendung beider Parameter von then() kann Probleme verursachen

// Don’t do this
asyncFunc1()
.then(
    value => { // (A)
        doSomething(); // (B)
        return asyncFunc2(); // (C)
    },
    error => { // (D)
        ···
    });

Der Ablehnungs-Callback (Zeile D) empfängt alle Ablehnungen von asyncFunc1(), aber er empfängt keine Ablehnungen, die vom Erfüllungs-Callback (Zeile A) erzeugt werden. Zum Beispiel kann der synchrone Funktionsaufruf in Zeile B eine Ausnahme auslösen oder der asynchrone Funktionsaufruf in Zeile C eine Ablehnung erzeugen.

Daher ist es besser, den Ablehnungs-Callback in ein verkettetes catch() zu verschieben

asyncFunc1()
.then(value => {
    doSomething();
    return asyncFunc2();
})
.catch(error => {
    ···
});

25.10 Tipps zur Fehlerbehandlung

25.10.1 Operationelle Fehler vs. Programmierfehler

In Programmen gibt es zwei Arten von Fehlern

25.10.1.1 Operationelle Fehler: keine Vermischung von Ablehnungen und Ausnahmen

Bei operationellen Fehlern sollte jede Funktion genau eine Methode zur Signalgebung von Fehlern unterstützen. Für Promise-basierte Funktionen bedeutet dies, Ablehnungen und Ausnahmen nicht zu vermischen, was dasselbe ist, wie zu sagen, dass sie keine Ausnahmen auslösen sollten.

25.10.1.2 Programmierfehler: Schnell scheitern

Bei Programmierfehlern kann es sinnvoll sein, so schnell wie möglich zu scheitern, indem man eine Ausnahme auslöst

function downloadFile(url) {
    if (typeof url !== 'string') {
        throw new Error('Illegal argument: ' + url);
    }
    return new Promise(···).
}

Wenn Sie dies tun, müssen Sie sicherstellen, dass Ihr asynchroner Code Ausnahmen verarbeiten kann. Ich halte das Auslösen von Ausnahmen für Assertionen und ähnliche Dinge für akzeptabel, die theoretisch statisch (z.B. über einen Linter, der den Quellcode analysiert) überprüft werden könnten.

25.10.2 Behandeln von Ausnahmen in Promise-basierten Funktionen

Wenn Ausnahmen innerhalb der Callbacks von then() und catch() ausgelöst werden, ist das kein Problem, da diese beiden Methoden sie in Ablehnungen umwandeln.

Die Dinge sind jedoch anders, wenn Sie Ihre asynchrone Funktion starten, indem Sie etwas Synchrones tun

function asyncFunc() {
    doSomethingSync(); // (A)
    return doSomethingAsync()
    .then(result => {
        ···
    });
}

Wenn in Zeile A eine Ausnahme ausgelöst wird, wirft die gesamte Funktion eine Ausnahme. Es gibt zwei Lösungen für dieses Problem.

25.10.2.1 Lösung 1: Rückgabe eines abgelehnten Promise

Sie können Ausnahmen abfangen und sie als abgelehnte Promises zurückgeben

function asyncFunc() {
    try {
        doSomethingSync();
        return doSomethingAsync()
        .then(result => {
            ···
        });
    } catch (err) {
        return Promise.reject(err);
    }
}
25.10.2.2 Lösung 2: Ausführen des synchronen Codes innerhalb eines Callbacks

Sie können auch eine Kette von then()-Methodenaufrufen über Promise.resolve() starten und den synchronen Code innerhalb eines Callbacks ausführen

function asyncFunc() {
    return Promise.resolve()
    .then(() => {
        doSomethingSync();
        return doSomethingAsync();
    })
    .then(result => {
        ···
    });
}

Eine Alternative ist, die Promise-Kette über den Promise-Konstruktor zu starten

function asyncFunc() {
    return new Promise((resolve, reject) => {
        doSomethingSync();
        resolve(doSomethingAsync());
    })
    .then(result => {
        ···
    });
}

Dieser Ansatz spart Ihnen einen Takt (der synchrone Code wird sofort ausgeführt), macht Ihren Code aber weniger regelmäßig.

25.10.3 Weiterführende Lektüre

Quellen dieses Abschnitts

25.11 Zusammensetzen von Promises

Zusammensetzen bedeutet, aus vorhandenen Teilen Neues zu schaffen. Wir sind bereits auf sequentielle Komposition von Promises gestoßen: Gegeben seien zwei Promises P und Q. Der folgende Code erzeugt ein neues Promise, das Q ausführt, nachdem P erfüllt wurde.

P.then(() => Q)

Beachten Sie, dass dies ähnlich wie das Semikolon für synchronen Code ist: Die sequentielle Komposition der synchronen Operationen f() und g() sieht wie folgt aus.

f(); g()

Dieser Abschnitt beschreibt zusätzliche Möglichkeiten, Promises zu komponieren.

25.11.1 Manuelles Aufteilen und Zusammenführen von Berechnungen

Nehmen wir an, Sie möchten zwei asynchrone Berechnungen, asyncFunc1() und asyncFunc2(), parallel durchführen

// Don’t do this
asyncFunc1()
.then(result1 => {
    handleSuccess({result1});
});
.catch(handleError);

asyncFunc2()
.then(result2 => {
    handleSuccess({result2});
})
.catch(handleError);

const results = {};
function handleSuccess(props) {
    Object.assign(results, props);
    if (Object.keys(results).length === 2) {
        const {result1, result2} = results;
        ···
    }
}
let errorCounter = 0;
function handleError(err) {
    errorCounter++;
    if (errorCounter === 1) {
        // One error means that everything failed,
        // only react to first error
        ···
    }
}

Die beiden Funktionsaufrufe asyncFunc1() und asyncFunc2() erfolgen ohne then()-Verkettung. Infolgedessen werden beide sofort und mehr oder weniger parallel ausgeführt. Die Ausführung ist nun verzweigt; jeder Funktionsaufruf hat einen separaten „Thread“ erzeugt. Sobald beide Threads beendet sind (mit einem Ergebnis oder einem Fehler), wird die Ausführung in entweder handleSuccess() oder handleError() zusammengeführt.

Das Problem bei diesem Ansatz ist, dass er zu viel manuelle und fehleranfällige Arbeit mit sich bringt. Die Lösung besteht darin, dies nicht selbst zu tun, indem man sich auf die eingebaute Methode Promise.all() verlässt.

25.11.2 Berechnungen per Promise.all() aufteilen und zusammenführen

Promise.all(iterable) nimmt ein Iterable von Promises entgegen (thenables und andere Werte werden über Promise.resolve() in Promises umgewandelt). Sobald alle erfüllt sind, wird es mit einem Array ihrer Werte erfüllt. Wenn iterable leer ist, wird das von all() zurückgegebene Promise sofort erfüllt.

Promise.all([
    asyncFunc1(),
    asyncFunc2(),
])
.then(([result1, result2]) => {
    ···
})
.catch(err => {
    // Receives first rejection among the Promises
    ···
});

25.11.3 map() per Promise.all()

Eine schöne Sache an Promises ist, dass viele synchrone Werkzeuge immer noch funktionieren, da Promise-basierte Funktionen Ergebnisse zurückgeben. Sie können zum Beispiel die Array-Methode map() verwenden.

const fileUrls = [
    'http://example.com/file1.txt',
    'http://example.com/file2.txt',
];
const promisedTexts = fileUrls.map(httpGet);

promisedTexts ist ein Array von Promises. Wir können Promise.all(), das wir bereits im vorherigen Abschnitt behandelt haben, verwenden, um dieses Array in ein Promise umzuwandeln, das mit einem Array von Ergebnissen erfüllt wird.

Promise.all(promisedTexts)
.then(texts => {
    for (const text of texts) {
        console.log(text);
    }
})
.catch(reason => {
    // Receives first rejection among the Promises
});

25.11.4 Timeout per Promise.race()

Promise.race(iterable) nimmt ein Iterable von Promises entgegen (thenables und andere Werte werden über Promise.resolve() in Promises umgewandelt) und gibt ein Promise P zurück. Das erste der Eingabe-Promises, das abgewickelt wird, übergibt seine Abwicklung an das Ausgabe-Promise. Wenn iterable leer ist, wird das von race() zurückgegebene Promise niemals abgewickelt.

Als Beispiel implementieren wir mit Promise.race() einen Timeout.

Promise.race([
    httpGet('http://example.com/file.txt'),
    delay(5000).then(function () {
        throw new Error('Timed out')
    });
])
.then(function (text) { ··· })
.catch(function (reason) { ··· });

25.12 Zwei nützliche zusätzliche Promise-Methoden

Dieser Abschnitt beschreibt zwei nützliche Methoden für Promises, die viele Promise-Bibliotheken anbieten. Sie werden nur gezeigt, um Promises weiter zu demonstrieren. Sie sollten sie nicht zu Promise.prototype hinzufügen (diese Art von Patching sollte nur von Polyfills durchgeführt werden).

25.12.1 done()

Wenn Sie mehrere Promise-Methodenaufrufe verketten, riskieren Sie, Fehler stillschweigend zu verwerfen. Zum Beispiel:

function doSomething() {
    asyncFunc()
    .then(f1)
    .catch(r1)
    .then(f2); // (A)
}

Wenn then() in Zeile A eine Ablehnung erzeugt, wird sie nirgends behandelt. Die Promise-Bibliothek Q bietet eine Methode done() an, die als letztes Element in einer Verkettung von Methodenaufrufen verwendet wird. Sie ersetzt entweder das letzte then() (und hat ein bis zwei Argumente).

function doSomething() {
    asyncFunc()
    .then(f1)
    .catch(r1)
    .done(f2);
}

Oder sie wird nach dem letzten then() eingefügt (und hat keine Argumente).

function doSomething() {
    asyncFunc()
    .then(f1)
    .catch(r1)
    .then(f2)
    .done();
}

Zitat aus der Q-Dokumentation:

Die Goldene Regel für die Verwendung von done gegenüber then lautet: Entweder geben Sie Ihr Promise an jemand anderen zurück, oder wenn die Kette bei Ihnen endet, rufen Sie done auf, um sie zu beenden. Die Beendigung mit catch ist nicht ausreichend, da der Catch-Handler selbst einen Fehler auslösen kann.

So würden Sie done() in ECMAScript 6 implementieren:

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected)
    .catch(function (reason) {
        // Throw an exception globally
        setTimeout(() => { throw reason }, 0);
    });
};

Obwohl die Funktionalität von done eindeutig nützlich ist, wurde sie nicht in ECMAScript 6 aufgenommen. Die Idee war, zuerst zu untersuchen, wie viel Engines automatisch erkennen können. Je nachdem, wie gut das funktioniert, könnte es notwendig werden, done() einzuführen.

25.12.2 finally()

Manchmal möchten Sie eine Aktion ausführen, unabhängig davon, ob ein Fehler aufgetreten ist oder nicht. Zum Beispiel, um aufzuräumen, nachdem Sie mit einer Ressource fertig sind. Dafür ist die Promise-Methode finally() da, die ähnlich wie die finally-Klausel in der Ausnahmebehandlung funktioniert. Ihr Callback erhält keine Argumente, wird aber über eine Auflösung oder Ablehnung benachrichtigt.

createResource(···)
.then(function (value1) {
    // Use resource
})
.then(function (value2) {
    // Use resource
})
.finally(function () {
    // Clean up
});

So schlägt Domenic Denicola vor, finally() zu implementieren:

Promise.prototype.finally = function (callback) {
    const P = this.constructor;
    // We don’t invoke the callback in here,
    // because we want then() to handle its exceptions
    return this.then(
        // Callback fulfills => continue with receiver’s fulfillment or rejec\
tion
        // Callback rejects => pass on that rejection (then() has no 2nd para\
meter!)
        value  => P.resolve(callback()).then(() => value),
        reason => P.resolve(callback()).then(() => { throw reason })
    );
};

Der Callback bestimmt, wie die Abwicklung des Empfängers (this) gehandhabt wird.

Beispiel 1 (von Jake Archibald): Verwendung von finally() zum Ausblenden eines Spinners. Vereinfachte Version.

showSpinner();
fetchGalleryData()
.then(data => updateGallery(data))
.catch(showNoDataError)
.finally(hideSpinner);

Beispiel 2 (von Kris Kowal): Verwendung von finally() zum Abbauen eines Tests.

const HTTP = require("q-io/http");
const server = HTTP.Server(app);
return server.listen(0)
.then(function () {
    // run test
})
.finally(server.stop);

25.13 Node.js: Verwendung von callback-basierten Sync-Funktionen mit Promises

Die Promise-Bibliothek Q verfügt über Hilfsfunktionen für die Interaktion mit Node.js-Style (err, result) Callback-APIs. Zum Beispiel wandelt denodeify eine callback-basierte Funktion in eine Promise-basierte um.

const readFile = Q.denodeify(FS.readFile);

readFile('foo.txt', 'utf-8')
.then(function (text) {
    ···
});

denodify ist eine Mikrobibliothek, die nur die Funktionalität von Q.denodeify() bereitstellt und mit der ECMAScript 6 Promise-API konform ist.

25.14 ES6-kompatible Promise-Bibliotheken

Es gibt viele Promise-Bibliotheken. Die folgenden entsprechen der ECMAScript 6 API, was bedeutet, dass Sie sie jetzt verwenden und später einfach auf native ES6 umsteigen können.

Minimale Polyfills

Größere Promise-Bibliotheken

ES6-Standardbibliotheks-Polyfills

25.15 Nächster Schritt: Promises über Generatoren verwenden

Die Implementierung von asynchronen Funktionen über Promises ist bequemer als über Events oder Callbacks, aber immer noch nicht ideal.

Die Lösung besteht darin, blockierende Aufrufe nach JavaScript zu bringen. Generatoren ermöglichen uns dies über Bibliotheken: Im folgenden Code verwende ich die Kontrollflussbibliothek co, um zwei JSON-Dateien asynchron abzurufen.

co(function* () {
    try {
        const [croftStr, bondStr] = yield Promise.all([  // (A)
            getFile('https://:8000/croft.json'),
            getFile('https://:8000/bond.json'),
        ]);
        const croftJson = JSON.parse(croftStr);
        const bondJson = JSON.parse(bondStr);

        console.log(croftJson);
        console.log(bondJson);
    } catch (e) {
        console.log('Failure to read: ' + e);
    }
});

In Zeile A blockiert die Ausführung (wartet) über yield, bis das Ergebnis von Promise.all() bereit ist. Das bedeutet, dass der Code synchron aussieht, während er asynchrone Operationen ausführt.

Details werden im Kapitel über Generatoren erläutert.

25.16 Promises im Detail: eine einfache Implementierung

In diesem Abschnitt nähern wir uns Promises aus einem anderen Blickwinkel: Anstatt zu lernen, wie die API verwendet wird, betrachten wir eine einfache Implementierung davon. Dieser andere Blickwinkel hat mir sehr geholfen, Promises zu verstehen.

Die Promise-Implementierung heißt DemoPromise. Um sie leichter verständlich zu machen, entspricht sie nicht vollständig der API. Aber sie ist nahe genug dran, um Ihnen dennoch viele Einblicke in die Herausforderungen zu geben, denen sich tatsächliche Implementierungen stellen.

DemoPromise ist eine Klasse mit drei Prototyp-Methoden:

Das heißt, resolve und reject sind Methoden (im Gegensatz zu Funktionen, die einem Callback-Parameter des Konstruktors übergeben werden).

25.16.1 Ein eigenständiges Promise

Unsere erste Implementierung ist ein eigenständiges Promise mit minimaler Funktionalität.

So wird diese erste Implementierung verwendet:

const dp = new DemoPromise();
dp.resolve('abc');
dp.then(function (value) {
    console.log(value); // abc
});

Das folgende Diagramm veranschaulicht, wie unsere erste DemoPromise funktioniert.

25.16.1.1 DemoPromise.prototype.then()

Betrachten wir zunächst then(). Es muss zwei Fälle behandeln:

then(onFulfilled, onRejected) {
    const self = this;
    const fulfilledTask = function () {
        onFulfilled(self.promiseResult);
    };
    const rejectedTask = function () {
        onRejected(self.promiseResult);
    };
    switch (this.promiseState) {
        case 'pending':
            this.fulfillReactions.push(fulfilledTask);
            this.rejectReactions.push(rejectedTask);
            break;
        case 'fulfilled':
            addToTaskQueue(fulfilledTask);
            break;
        case 'rejected':
            addToTaskQueue(rejectedTask);
            break;
    }
}

Der vorherige Codeausschnitt verwendet die folgende Hilfsfunktion:

function addToTaskQueue(task) {
    setTimeout(task, 0);
}
25.16.1.2 DemoPromise.prototype.resolve()

resolve() funktioniert wie folgt: Wenn das Promise bereits abgewickelt ist, tut es nichts (stellt sicher, dass ein Promise nur einmal abgewickelt werden kann). Andernfalls ändert sich der Zustand des Promises zu 'fulfilled' und das Ergebnis wird in this.promiseResult zwischengespeichert. Als Nächstes werden alle bisher in die Warteschlange gestellten Erfüllungsreaktionen ausgelöst.

resolve(value) {
    if (this.promiseState !== 'pending') return;
    this.promiseState = 'fulfilled';
    this.promiseResult = value;
    this._clearAndEnqueueReactions(this.fulfillReactions);
    return this; // enable chaining
}
_clearAndEnqueueReactions(reactions) {
    this.fulfillReactions = undefined;
    this.rejectReactions = undefined;
    reactions.map(addToTaskQueue);
}

reject() ist ähnlich wie resolve().

25.16.2 Verkettung

Das nächste Merkmal, das wir implementieren, ist die Verkettung.

Offensichtlich ändert sich nur then().

then(onFulfilled, onRejected) {
    const returnValue = new Promise(); // (A)
    const self = this;

    let fulfilledTask;
    if (typeof onFulfilled === 'function') {
        fulfilledTask = function () {
            const r = onFulfilled(self.promiseResult);
            returnValue.resolve(r); // (B)
        };
    } else {
        fulfilledTask = function () {
            returnValue.resolve(self.promiseResult); // (C)
        };
    }

    let rejectedTask;
    if (typeof onRejected === 'function') {
        rejectedTask = function () {
            const r = onRejected(self.promiseResult);
            returnValue.resolve(r); // (D)
        };
    } else {
        rejectedTask = function () {
            // `onRejected` has not been provided
            // => we must pass on the rejection
            returnValue.reject(self.promiseResult); // (E)
        };
    }
    ···
    return returnValue; // (F)
}

then() erstellt und gibt ein neues Promise zurück (Zeilen A und F). Zusätzlich werden fulfilledTask und rejectedTask anders eingerichtet: Nach einer Abwicklung...

25.16.3 Abflachung

Abflachung dient hauptsächlich dazu, die Verkettung bequemer zu gestalten: Normalerweise gibt die Rückgabe eines Werts aus einer Reaktion ihn an das nächste then() weiter. Wenn wir ein Promise zurückgeben, wäre es schön, wenn es für uns „ausgepackt“ werden könnte, wie im folgenden Beispiel.

asyncFunc1()
.then(function (value1) {
    return asyncFunc2(); // (A)
})
.then(function (value2) {
    // value2 is fulfillment value of asyncFunc2() Promise
    console.log(value2);
});

Wir haben in Zeile A ein Promise zurückgegeben und mussten keinen Aufruf von then() in die aktuelle Methode verschachteln. Wir konnten then() auf dem Ergebnis der Methode aufrufen. Somit: keine verschachtelten then(), alles bleibt flach.

Dies implementieren wir, indem wir die Methode resolve() die Abflachung durchführen lassen.

Wir können die Abflachung generischer gestalten, wenn wir zulassen, dass Q ein Thenable ist (anstatt nur ein Promise).

Um die Sperrung zu implementieren, führen wir ein neues boolesches Flag this.alreadyResolved ein. Sobald es wahr ist, ist this gesperrt und kann nicht mehr aufgelöst werden. Beachten Sie, dass this immer noch ausstehend sein kann, da sein Zustand jetzt derselbe ist wie der des Promises, auf das es gesperrt ist.

resolve(value) {
    if (this.alreadyResolved) return;
    this.alreadyResolved = true;
    this._doResolve(value);
    return this; // enable chaining
}

Die eigentliche Auflösung erfolgt nun in der privaten Methode _doResolve().

_doResolve(value) {
    const self = this;
    // Is `value` a thenable?
    if (typeof value === 'object' && value !== null && 'then' in value) {
        // Forward fulfillments and rejections from `value` to `this`.
        // Added as a task (versus done immediately) to preserve async semant\
ics.
        addToTaskQueue(function () { // (A)
            value.then(
                function onFulfilled(result) {
                    self._doResolve(result);
                },
                function onRejected(error) {
                    self._doReject(error);
                });
        });
    } else {
        this.promiseState = 'fulfilled';
        this.promiseResult = value;
        this._clearAndEnqueueReactions(this.fulfillReactions);
    }
}

Die Abflachung erfolgt in Zeile A: Wenn value erfüllt ist, wollen wir, dass self erfüllt wird, und wenn value abgelehnt ist, wollen wir, dass self abgelehnt wird. Die Weiterleitung erfolgt über die privaten Methoden _doResolve und _doReject, um die Absicherung über alreadyResolved zu umgehen.

25.16.4 Promise-Zustände im Detail

Mit der Verkettung werden die Zustände von Promises komplexer (wie in Abschnitt 25.4 der ECMAScript 6 Spezifikation beschrieben).

Wenn Sie Promises nur *verwenden*, können Sie normalerweise eine vereinfachte Weltsicht annehmen und das Sperren ignorieren. Das wichtigste zustandsbezogene Konzept bleibt die „Abwicklung“: Ein Promise ist abgewickelt, wenn es entweder erfüllt oder abgelehnt wurde. Nachdem ein Promise abgewickelt wurde, ändert es sich nicht mehr (Zustand und Erfüllungs- oder Ablehnungswert).

Wenn Sie Promises *implementieren* möchten, dann ist „Auflösen“ auch wichtig und jetzt schwerer zu verstehen.

25.16.5 Ausnahmen

Als letztes Merkmal möchten wir, dass unsere Promises Ausnahmen im Benutzercode als Ablehnungen behandeln. Vorerst bedeutet „Benutzercode“ die beiden Callback-Parameter von then().

Der folgende Auszug zeigt, wie wir Ausnahmen innerhalb von onFulfilled in Ablehnungen umwandeln – indem wir einen try-catch um ihren Aufruf in Zeile A wickeln.

then(onFulfilled, onRejected) {
    ···
    let fulfilledTask;
    if (typeof onFulfilled === 'function') {
        fulfilledTask = function () {
            try {
                const r = onFulfilled(self.promiseResult); // (A)
                returnValue.resolve(r);
            } catch (e) {
                returnValue.reject(e);
            }
        };
    } else {
        fulfilledTask = function () {
            returnValue.resolve(self.promiseResult);
        };
    }
    ···
}

25.16.6 Revealing Constructor Pattern

Wenn wir DemoPromise in eine tatsächliche Promise-Implementierung umwandeln wollten, müssten wir immer noch das Revealing Constructor Pattern [2] implementieren: ES6 Promises werden nicht über Methoden aufgelöst und abgelehnt, sondern über Funktionen, die dem Executor, dem Callback-Parameter des Konstruktors, übergeben werden.

Wenn der Executor eine Ausnahme auslöst, muss „sein“ Promise abgelehnt werden.

25.17 Vorteile und Einschränkungen von Promises

25.17.1 Vorteile von Promises

25.17.1.1 Vereinheitlichung von asynchronen APIs

Ein wichtiger Vorteil von Promises ist, dass sie zunehmend von asynchronen Browser-APIs verwendet werden und derzeit unterschiedliche und inkompatible Muster und Konventionen vereinheitlichen werden. Sehen wir uns zwei kommende Promise-basierte APIs an.

Die Fetch API ist eine Promise-basierte Alternative zu XMLHttpRequest.

fetch(url)
.then(request => request.text())
.then(str => ···)

fetch() gibt ein Promise für die eigentliche Anfrage zurück, text() gibt ein Promise für den Inhalt als String zurück.

Die ECMAScript 6 API für programmatisches Importieren von Modulen basiert ebenfalls auf Promises.

System.import('some_module.js')
.then(some_module => {
    ···
})
25.17.1.2 Promises vs. Events

Im Vergleich zu Events eignen sich Promises besser für die Behandlung von einmaligen Ergebnissen. Es spielt keine Rolle, ob Sie sich für ein Ergebnis registrieren, bevor oder nachdem es berechnet wurde, Sie erhalten es. Dieser Vorteil von Promises ist fundamentaler Natur. Auf der anderen Seite können Sie sie nicht für die Behandlung wiederkehrender Ereignisse verwenden. Die Verkettung ist ein weiterer Vorteil von Promises, aber einer, der dem Ereignishandling hinzugefügt werden könnte.

25.17.1.3 Promises vs. Callbacks

Im Vergleich zu Callbacks haben Promises sauberere Funktions- (oder Methoden-) Signaturen. Bei Callbacks werden Parameter für Eingabe und Ausgabe verwendet.

fs.readFile(name, opts?, (err, string | Buffer) => void)

Bei Promises werden alle Parameter für die Eingabe verwendet.

readFilePromisified(name, opts?) : Promise<string | Buffer>

Zusätzliche Vorteile von Promises umfassen:

25.17.2 Promises sind nicht immer die beste Wahl

Promises eignen sich gut für einzelne asynchrone Ergebnisse. Sie sind nicht geeignet für:

ECMAScript 6 Promises fehlen zwei Funktionen, die manchmal nützlich sind:

Die Q Promise-Bibliothek hat Unterstützung für Letzteres, und es gibt Pläne, beide Fähigkeiten zu Promises/A+ hinzuzufügen.

25.18 Referenz: die ECMAScript 6 Promise API

Dieser Abschnitt gibt einen Überblick über die ECMAScript 6 Promise API, wie in der Spezifikation beschrieben.

25.18.1 Promise Konstruktor

Der Konstruktor für Promises wird wie folgt aufgerufen:

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

Der Callback dieses Konstruktors wird als Executor bezeichnet. Der Executor kann seine Parameter verwenden, um das neue Promise p aufzulösen oder abzulehnen.

25.18.2 Statische Promise-Methoden

25.18.2.1 Promises erstellen

Die folgenden beiden statischen Methoden erstellen neue Instanzen ihrer Empfänger:

25.18.2.2 Promises komponieren

Intuitiv komponieren die statischen Methoden Promise.all() und Promise.race() Iterables von Promises zu einem einzigen Promise. Das heißt:

Die Methoden sind:

25.18.3 Promise.prototype-Methoden

25.18.3.1 Promise.prototype.then(onFulfilled, onRejected)

Standardwerte für ausgelassene Reaktionen könnten wie folgt implementiert werden:

function defaultOnFulfilled(x) {
    return x;
}
function defaultOnRejected(e) {
    throw e;
}
25.18.3.2 Promise.prototype.catch(onRejected)

25.19 Weitere Lektüre

[1] „Promises/A+“, herausgegeben von Brian Cavalier und Domenic Denicola (der De-facto-Standard für JavaScript Promises).

[2] „The Revealing Constructor Pattern“ von Domenic Denicola (dieses Muster wird vom Promise-Konstruktor verwendet).

Weiter: VI Sonstiges