web-dev-qa-db-fra.com

Y a-t-il encore des raisons d'utiliser des bibliothèques de promesses comme Q ou BlueBird maintenant que nous avons des promesses ES6?

Après que Node.js ait ajouté la prise en charge native des promesses, existe-t-il encore des raisons d'utiliser des bibliothèques comme Q ou BlueBird?

Par exemple, si vous démarrez un nouveau projet et supposons que, dans ce projet, aucune dépendance n'utilise ces bibliothèques, pouvons-nous dire qu'il n'y a plus vraiment de raison d'utiliser de telles bibliothèques?

213
Murat Ozgul

Le vieil adage dit que vous devriez choisir le bon outil pour le travail. Les promesses ES6 fournissent les bases. Si tout ce que vous voulez ou dont vous avez besoin, ce sont les bases, alors cela devrait/pourrait très bien fonctionner pour vous. Mais il y a plus d'outils dans la corbeille à outils que les bases et il existe des situations où ces outils supplémentaires sont très utiles. Et, je dirais que les promesses de l’ES6 manquent même certains principes de base, comme la promesse, qui sont utiles dans à peu près tous les projets node.js.

Je connais très bien le bibliothèque de promesses Bluebird , je parlerai donc principalement de mon expérience avec cette bibliothèque.

Donc, voici mes 6 principales raisons d'utiliser une bibliothèque Promise plus performante

  1. Interfaces asynchrones non promisifiées - .promisify() et .promisifyAll() sont incroyablement utiles pour gérer toutes les interfaces asynchrones qui nécessitent encore des rappels en clair et ne renvoient pas encore leurs promesses - un La ligne de code crée une version promise d'une interface complète.

  2. Plus rapide - Bluebird est nettement plus rapide que les promesses natives dans la plupart des environnements.

  3. Séquencement de l'itération de tableau asynchrone - Promise.mapSeries() ou Promise.reduce() vous permet de parcourir un tableau en appelant une opération asynchrone sur chaque élément, mais en séquençant les opérations async se produisent les uns après les autres, pas tous en même temps. Vous pouvez le faire soit parce que le serveur de destination l’exige, soit parce que vous devez transmettre un résultat à un autre.

  4. Polyfill - Si vous souhaitez utiliser les promesses dans les anciennes versions des clients de navigateur, vous aurez tout de même besoin d'un polyfill. Peut aussi bien obtenir un polyfill capable. Puisque node.js a des promesses ES6, vous n'avez pas besoin d'un polyfill dans node.js, mais vous pouvez le faire dans un navigateur. Si vous codez à la fois le serveur et le client node.js, il peut s'avérer très utile de disposer de la même bibliothèque de promesses et des mêmes fonctionnalités (partage plus facile du code, changement de contexte entre environnements, utilisation de techniques de codage communes pour le code async, etc.). .).

  5. Autres fonctionnalités utiles - Bluebird a Promise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each() et Promise.props(), qui sont parfois maniables. Ces opérations peuvent être exécutées avec les promesses ES6 et du code supplémentaire, mais Bluebird est déjà livré avec ces opérations prédéfinies et prétestées. Il est donc plus simple et moins codé de les utiliser.

  6. Avertissements intégrés et traces de pile complètes - Bluebird propose un certain nombre d'avertissements intégrés qui vous avertissent de problèmes qui constituent probablement un code incorrect ou un bogue. Par exemple, si vous appelez une fonction qui crée une nouvelle promesse dans un gestionnaire .then() sans renvoyer cette promesse (pour la lier à la chaîne de promesse actuelle), il s'agit dans la plupart des cas d'un bug accidentel et Bluebird vous en avertit. cet effet. Les autres avertissements Bluebird intégrés sont décrits ici .

Voici quelques détails supplémentaires sur ces différents sujets:

PromisifyAll

Dans tous les projets node.js, j’utilise immédiatement Bluebird partout car j’utilise beaucoup .promisifyAll() sur des modules node.js standard comme le module fs.

Node.js ne fournit pas en soi une interface de promesse aux modules intégrés qui font async IO comme le module fs. Donc, si vous voulez utiliser des promesses avec ces interfaces, il vous reste soit à coder un encapsuleur de promesse autour de chaque fonction de module que vous utilisez, soit à obtenir une bibliothèque qui peut le faire pour vous ou ne pas utiliser de promesses.

Les fonctions Promise.promisify() et Promise.promisifyAll() de Bluebird fournissent un habillage automatique des API asynchrones de convention d'appel node.js pour renvoyer des promesses. C'est extrêmement utile et permet de gagner du temps. Je l'utilise tout le temps.

Voici un exemple de la façon dont cela fonctionne:

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

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

L’alternative serait de créer manuellement votre propre wrapper de promesse pour chaque API fs que vous souhaitiez utiliser:

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
});

Et vous devez le faire manuellement pour chaque fonction API que vous souhaitez utiliser. Cela n'a clairement aucun sens. C'est le code standard. Vous pourriez aussi bien obtenir un utilitaire qui fonctionne pour vous. Les Promise.promisify() et Promise.promisifyAll() de Bluebird sont un tel utilitaire.

Autres fonctionnalités utiles

Voici certaines des fonctionnalités de Bluebird que je trouve particulièrement utiles (voici quelques exemples de code sur la manière dont ils peuvent économiser du code ou accélérer le développement):

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

En plus de sa fonction utile, Promise.map() prend également en charge une option d'accès simultané qui vous permet de spécifier le nombre d'opérations pouvant être autorisées simultanément, ce qui est particulièrement utile lorsque vous avez beaucoup à faire, mais que vous ne pouvez pas surcharger certaines opérations. ressource extérieure.

Certains d'entre eux peuvent être appelés à la fois autonomes et utilisés sur une promesse qui se résout elle-même en un itératif qui peut économiser beaucoup de code.


Polyfill

Dans un projet de navigateur, étant donné que vous souhaitez généralement continuer à prendre en charge certains navigateurs qui ne prennent pas en charge Promise, vous aurez quand même besoin d'un remplissage multiple. Si vous utilisez également jQuery, vous pouvez parfois simplement utiliser le support de promesse intégré à jQuery (bien qu'il soit douloureusement non standard à certains égards, peut-être corrigé dans jQuery 3.0), mais si le projet implique une activité asynchrone importante, les fonctionnalités étendues de Bluebird sont très utiles.


plus rapide

Il convient également de noter que les promesses de Bluebird semblent être nettement plus rapides que celles promises dans V8. Voir ce post pour plus de discussion sur ce sujet.


Il manque un gros nœud.js

Ce qui me ferait penser à utiliser moins Bluebird dans le développement de node.js, ce serait si node.js était intégré à une fonction promisify afin que vous puissiez faire quelque chose comme ceci:

const fs = requirep('fs');

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

Ou tout simplement proposer des méthodes déjà promises dans le cadre des modules intégrés.

Jusque-là, je le fais avec Bluebird:

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

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

Il semble un peu étrange de prendre en charge la prise en charge de promesses ES6 intégrée dans node.js et de ne renvoyer aucune promesse par les modules intégrés. Cela doit être trié dans node.js. Jusque-là, j'utilise Bluebird pour promettre des bibliothèques entières. Donc, il semble que les promesses soient implémentées à environ 20% dans node.js, car aucun des modules intégrés ne vous permet de les utiliser sans les emballer manuellement au préalable.


Exemples

Voici un exemple de Promises simples contre les promisify et Promise.map() de Bluebird pour lire un ensemble de fichiers en parallèle et en avertir lorsque vous avez terminé avec toutes les données:

Plain Promises

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() et Promise.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
});

Voici un exemple de Promises simples contre les promisify et Promise.map() de Bluebird lors de la lecture d'un ensemble d'URL depuis un hôte distant où vous pouvez en lire au maximum 4 à la fois, tout en souhaitant conserver autant de demandes en parallèle que autorisé:

Plain JS Promises

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 Promises

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
});
352
jfriend00