Gibt es noch Gründe, Versprechen-Bibliotheken wie Q oder BlueBird zu verwenden, nachdem wir ES6-Versprechen haben? [geschlossen]

228

Gibt es noch Gründe, Bibliotheken wie Q oder BlueBird zu verwenden, nachdem Node.js native Unterstützung für Versprechen hinzugefügt hat?

Wenn Sie beispielsweise ein neues Projekt starten und in diesem Projekt davon ausgehen, dass Sie keine Abhängigkeiten haben, die diese Bibliotheken verwenden, können wir dann sagen, dass es wirklich keine Gründe mehr gibt, solche Bibliotheken zu verwenden?

Murat Ozgul
quelle
4
native Versprechen haben sehr, sehr grundlegende Eigenschaften. Bibliotheken wie Q oder Bluebird fügen eine Menge mehr hinzu. Wenn Sie diese Funktionen benötigen, verwenden Sie diese Bibliotheken.
Gman
7
Ich habe den Titel so bearbeitet, dass es weniger um "Notwendigkeit" als vielmehr um "Gründe für die Verwendung von Versprechensbibliotheken" geht. Diese Frage kann beantwortet werden, indem in erster Linie Fakten und keine Meinungen angegeben werden. Es sollte wieder geöffnet werden, da es durch die Angabe von Fakten und nicht in erster Linie durch Meinungen beantwortet werden kann. Siehe die Antwort unten als Demonstration dafür.
jfriend00
11
@JaromandaX - Bitte erwägen Sie die Wiedereröffnung jetzt, da Titel und Frage so angepasst wurden, dass es mehr darum geht, warum eine Versprechungsbibliothek verwendet wird, als ob eine Versprechungsbibliothek "benötigt" wird. Meiner Meinung nach kann diese Frage durch die Angabe von Fakten und nicht in erster Linie durch die Stellungnahme beantwortet werden - siehe Antwort unten als Demonstration dafür.
jfriend00
6
Diese Frage nach der Titelbearbeitung und ihre akzeptierte Antwort basieren nicht auf Meinungen.
Max
7
Einverstanden. Dies ist eine absolut gültige Frage in ihrer aktuellen Form. Ich habe für die Wiedereröffnung nominiert.
Jules

Antworten:

367

Das alte Sprichwort besagt, dass Sie das richtige Werkzeug für den Job auswählen sollten. ES6-Versprechen liefern die Grundlagen. Wenn alles, was Sie jemals wollen oder brauchen, die Grundlagen sind, dann sollte / könnte das für Sie gut funktionieren. Es gibt jedoch mehr Werkzeuge im Werkzeugkorb als nur die Grundlagen, und es gibt Situationen, in denen diese zusätzlichen Werkzeuge sehr nützlich sind. Und ich würde argumentieren, dass ES6-Versprechen sogar einige der Grundlagen wie die Versprechen fehlen, die in so ziemlich jedem node.js-Projekt nützlich sind.

Ich bin mit der Bluebird-Versprechensbibliothek am besten vertraut, daher werde ich hauptsächlich aus meiner Erfahrung mit dieser Bibliothek sprechen.

Hier sind meine sechs wichtigsten Gründe, eine leistungsfähigere Promise-Bibliothek zu verwenden

  1. Nicht versprochene asynchrone Schnittstellen - .promisify()und .promisifyAll()sind unglaublich nützlich, um all jene asynchronen Schnittstellen zu handhaben, die noch einfache Rückrufe erfordern und noch keine Versprechen zurückgeben - eine Codezeile erstellt eine versprochene Version einer gesamten Schnittstelle.

  2. Schneller - Bluebird ist in den meisten Umgebungen deutlich schneller als native Versprechen.

  3. Sequenzierung der Iteration eines asynchronen Arrays - Promise.mapSeries()oder Promise.reduce()Sie können ein Array durchlaufen, indem Sie für jedes Element eine asynchrone Operation aufrufen, die asynchronen Operationen jedoch so sequenzieren, dass sie nacheinander und nicht alle gleichzeitig ausgeführt werden. Sie können dies entweder tun, weil der Zielserver dies erfordert oder weil Sie ein Ergebnis an das nächste übergeben müssen.

  4. Polyfill - Wenn Sie Versprechen in älteren Versionen von Browser-Clients verwenden möchten, benötigen Sie ohnehin eine Polyfill. Kann auch eine fähige Polyfüllung bekommen. Da node.js ES6-Versprechen hat, benötigen Sie keine Polyfüllung in node.js, aber möglicherweise in einem Browser. Wenn Sie sowohl den Server als auch den Client von node.js codieren, kann es sehr nützlich sein, in beiden dieselbe Versprechungsbibliothek und dieselben Funktionen zu haben (einfacherer Code-Austausch, Kontextwechsel zwischen Umgebungen, Verwendung gängiger Codierungstechniken für asynchronen Code usw.) .).

  5. Weitere nützliche Funktionen - Drossel hat Promise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each()und Promise.props()alle sind gelegentlich praktisch. Während diese Vorgänge mit ES6-Versprechungen und zusätzlichem Code ausgeführt werden können, enthält Bluebird diese Vorgänge bereits vorgefertigt und vorab getestet, sodass die Verwendung einfacher und weniger Code ist.

  6. Eingebaute Warnungen und vollständige Stapelspuren - Bluebird verfügt über eine Reihe integrierter Warnungen, die Sie auf Probleme aufmerksam machen, bei denen es sich wahrscheinlich um falschen Code oder einen Fehler handelt. Wenn Sie beispielsweise eine Funktion aufrufen, die ein neues Versprechen in einem .then()Handler erstellt, ohne dieses Versprechen zurückzugeben (um es mit der aktuellen Versprechen-Kette zu verknüpfen), handelt es sich in den meisten Fällen um einen versehentlichen Fehler, und Bluebird gibt Ihnen eine Warnung dazu aus bewirken. Weitere integrierte Bluebird-Warnungen werden hier beschrieben .

Hier einige Details zu diesen verschiedenen Themen:

PromisifyAll

In jedem node.js-Projekt verwende ich Bluebird sofort überall, da ich häufig .promisifyAll()Standardmodule von node.js wie das fsModul verwende.

Node.js selbst bietet keine vielversprechende Schnittstelle zu den integrierten Modulen, die wie das fs Modul asynchrone E / A-Vorgänge ausführen . Wenn Sie also Versprechen mit diesen Schnittstellen verwenden möchten, müssen Sie entweder einen Versprechen-Wrapper für jede von Ihnen verwendete Modulfunktion von Hand codieren oder eine Bibliothek erhalten, die dies für Sie tun kann oder keine Versprechen verwendet.

Bluebirds Promise.promisify()und Promise.promisifyAll()bieten eine automatische Umhüllung von node.js, die konventionelle asynchrone APIs aufrufen, um Versprechen zurückzugeben. Es ist äußerst nützlich und zeitsparend. Ich benutze es die ganze Zeit.

Hier ist ein Beispiel, wie das funktioniert:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

Die Alternative wäre, manuell einen eigenen Versprechen-Wrapper für jede fsAPI zu erstellen, die Sie verwenden möchten :

const fs = require('fs');

function readFileAsync(file, options) {
    return new Promise(function(resolve, reject) {
        fs.readFile(file, options, function(err, data) {
            if (err) {
                reject(err);
            } else {
                 resolve(data);
            }
        });
    });
}

readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

Und Sie müssen dies manuell für jede API-Funktion tun, die Sie verwenden möchten. Das macht eindeutig keinen Sinn. Es ist Boilerplate-Code. Sie können auch ein Dienstprogramm erwerben, das diese Aufgabe für Sie erledigt. Bluebird's Promise.promisify()und Promise.promisifyAll()sind so ein Dienstprogramm.

Andere nützliche Funktionen

Hier sind einige der Bluebird-Funktionen, die ich besonders nützlich finde (im Folgenden finden Sie einige Codebeispiele, wie diese Code speichern oder die Entwicklung beschleunigen können):

Promise.promisify()
Promise.promisifyAll()
Promise.map()
Promise.reduce()
Promise.mapSeries()
Promise.delay()

Zusätzlich zu seiner nützlichen Funktion wird Promise.map()auch eine Parallelitätsoption unterstützt, mit der Sie festlegen können, wie viele Vorgänge gleichzeitig ausgeführt werden sollen. Dies ist besonders nützlich, wenn Sie viel zu tun haben, aber einige außerhalb nicht überwältigen können Ressource.

Einige davon können sowohl als eigenständig bezeichnet als auch für ein Versprechen verwendet werden, das sich selbst in eine iterierbare Version auflöst, die viel Code sparen kann.


Polyfill

In einem Browserprojekt benötigen Sie ohnehin eine Polyfüllung, da Sie im Allgemeinen einige Browser unterstützen möchten, die keine Promise-Unterstützung bieten. Wenn Sie auch jQuery verwenden, können Sie manchmal einfach die in jQuery integrierte Versprechensunterstützung verwenden (obwohl dies in gewisser Weise schmerzlich nicht dem Standard entspricht, möglicherweise in jQuery 3.0 behoben), aber wenn das Projekt eine signifikante asynchrone Aktivität beinhaltet, finde ich Die erweiterten Funktionen in Bluebird sind sehr nützlich.


Schneller

Erwähnenswert ist auch, dass Bluebirds Versprechen deutlich schneller zu sein scheinen als die in V8 integrierten Versprechen. In diesem Beitrag finden Sie weitere Informationen zu diesem Thema.


Ein Big Thing Node.js fehlt

Was mich dazu bringen würde, Bluebird weniger in der Entwicklung von node.js zu verwenden, wäre, wenn node.js in eine Promisify-Funktion eingebaut wäre, damit Sie so etwas tun könnten:

const fs = requirep('fs');

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

Oder bieten Sie einfach bereits versprochene Methoden als Teil der eingebauten Module an.

Bis dahin mache ich das mit Bluebird:

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync('somefile.text').then(function(data) {
   // do something with data here
});

Es scheint ein bisschen seltsam zu sein, dass die Unterstützung von ES6-Versprechen in node.js integriert ist und keines der integrierten Module Versprechen zurückgibt. Dies muss in node.js aussortiert werden. Bis dahin benutze ich Bluebird, um ganze Bibliotheken zu versprechen. Es scheint also, dass Versprechen jetzt zu etwa 20% in node.js implementiert sind, da Sie mit keinem der integrierten Module Versprechen mit ihnen verwenden können, ohne sie zuerst manuell zu verpacken.


Beispiele

Hier ist ein Beispiel für einfache Versprechen im Vergleich zu Bluebirds Versprechen und Promise.map()zum parallelen Lesen einer Reihe von Dateien und zum Benachrichtigen, wenn alle Daten fertig sind:

Einfache Versprechen

const files = ["file1.txt", "fileA.txt", "fileB.txt"];
const fs = require('fs');

// make promise version of fs.readFile()
function fsReadFileP(file, options) {
    return new Promise(function(resolve, reject) {
        fs.readFile(file, options, function(err, data) {
            if (err) return reject(err);
            resolve(data);
        });
    });
}


Promise.all(files.map(fsReadFileP)).then(function(results) {
    // files data in results Array
}, function(err) {
    // error here
});

Bluebird Promise.map()undPromise.promisifyAll()

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const files = ["file1.txt", "fileA.txt", "fileB.txt"];

Promise.map(files, fs.readFileAsync).then(function(results) {
    // files data in results Array
}, function(err) {
    // error here
});

Hier ist ein Beispiel für einfache Versprechen im Vergleich zu Bluebirds Versprechen und Promise.map()beim Lesen einer Reihe von URLs von einem Remote-Host, auf dem Sie höchstens 4 gleichzeitig lesen können, aber so viele Anfragen wie möglich parallel halten möchten:

Einfache JS-Versprechen

const request = require('request');
const urls = [url1, url2, url3, url4, url5, ....];

// make promisified version of request.get()
function requestGetP(url) {
    return new Promise(function(resolve, reject) {
        request.get(url, function(err, data) {
            if (err) return reject(err);
            resolve(data);
        });
    });
}

function getURLs(urlArray, concurrentLimit) {
    var numInFlight = 0;
    var index = 0;
    var results = new Array(urlArray.length);
    return new Promise(function(resolve, reject) {
        function next() {
            // load more until concurrentLimit is reached or until we got to the last one
            while (numInFlight < concurrentLimit && index < urlArray.length) {
                (function(i) {
                    requestGetP(urlArray[index++]).then(function(data) {
                        --numInFlight;
                        results[i] = data;
                        next();
                    }, function(err) {
                        reject(err);
                    });
                    ++numInFlight;
                })(index);
            }
            // since we always call next() upon completion of a request, we can test here
            // to see if there was nothing left to do or finish
            if (numInFlight === 0 && index === urlArray.length) {
                resolve(results);
            }
        }
        next();
    });
}

Bluebird verspricht

const Promise = require('bluebird');
const request = Promise.promisifyAll(require('request'));
const urls = [url1, url2, url3, url4, url5, ....];

Promise.map(urls, request.getAsync, {concurrency: 4}).then(function(results) {
    // urls fetched in order in results Array
}, function(err) {
    // error here
});
jfriend00
quelle
obwohl es in gewisser Hinsicht schmerzlich nicht dem Standard entspricht - Sie behaupten, dass sie jetzt "Promises / A + kompatibel" sind :) - blog.jquery.com/2016/01/14/jquery-3-0-beta-released
thefourtheye
1
@thefourtheye - Ja, ich weiß, dass sie in Version 3.0 auf Promise / A + -Kompatibilität hingearbeitet haben. Aber das ist noch in der Beta. Wenn es das Versprechen einhält (Wortspiel beabsichtigt), kann es einen Teil des Grundes für die Verwendung einer externen Versprechungsbibliothek in Browser JS vermeiden, wenn Sie bereits jQuery verwendet haben. Es wird immer noch nicht alle nützlichen Funktionen bieten, die Bluebird bietet, und ich wäre äußerst überrascht, wenn es der Leistung von Bluebird gerecht wird, sodass in einigen Fällen neben einer zukünftigen jQuery noch Platz für Bluebird vorhanden ist. In jedem Fall scheint sich die Frage des OP hauptsächlich auf node.js zu beziehen.
jfriend00
1
Der letzte Beispielcode enthält einen kleinen Tippfehler : return new Promise(function(resolve, rejct). Sollte sein:reject
Sebastian Muszyński
7
Node.js hat util.promisifyjetzt tatsächlich , obwohl es kein direktes promisifyAllÄquivalent gibt.
Nyuszika7h
1
@Aurast - Ja, v11 kümmert sich darum fs, aber noch einige andere Gründe, Bluebird zu verwenden (mein besonderer Favorit ist die concurrencyOption in Promise.map()), um zu verhindern, dass ein Zieldienst überfordert wird, an den Sie eine Reihe paralleler Anfragen stellen müssen. Auch noch viele andere nicht versprochene Schnittstellen, mit denen Bluebirds promisifyAll verwendet werden kann. Aber langsam verblassen die Gründe, bei jedem neuen Projekt sofort nach Bluebird zu greifen, da node.js selbst seine integrierte Versprechen-Unterstützung verstärkt.
jfriend00