¿Todavía hay razones para usar bibliotecas de promesas como Q o BlueBird ahora que tenemos promesas de ES6? [cerrado]

229

Después de que Node.js agregó soporte nativo para las promesas, ¿todavía hay razones para usar bibliotecas como Q o BlueBird?

Por ejemplo, si está comenzando un nuevo proyecto y supongamos que en este proyecto no tiene dependencias que usen estas bibliotecas, ¿podemos decir que realmente no hay más razones para usar tales bibliotecas?

Murat Ozgul
fuente
44
Las promesas nativas tienen características muy muy básicas. Las bibliotecas como Q o Bluebird agregan un montón más. Si necesita esas características, use esas bibliotecas.
gman
77
Edité el título para hacerlo menos sobre "necesidad" y más sobre "razones para seguir usando bibliotecas prometedoras". Esta pregunta puede ser respondida principalmente proporcionando hechos, no opiniones. Debe reabrirse porque puede responderse aportando hechos y no principalmente opiniones. Vea la respuesta a continuación como una demostración de eso.
jfriend00
11
@JaromandaX: considere reabrir ahora que el título y la pregunta se han modificado para que se trate más de por qué se usaría una biblioteca de promesas en lugar de si uno "necesita" usar una biblioteca de promesas. En mi opinión, esta pregunta se puede responder aportando hechos y no principalmente opiniones; vea la respuesta a continuación como una demostración de eso.
jfriend00
66
Esta pregunta después de la edición del título, y su respuesta aceptada, no están basadas en opiniones.
max
77
Convenido. Esta es una pregunta perfectamente válida en su forma actual. He nominado para reabrir.
Jules

Respuestas:

368

El viejo adagio dice que debes elegir la herramienta adecuada para el trabajo. Las promesas de ES6 proporcionan lo básico. Si todo lo que quiere o necesita es lo básico, entonces eso debería / podría funcionar bien para usted. Pero, hay más herramientas en el contenedor de herramientas que solo los básicos y hay situaciones en las que esas herramientas adicionales son muy útiles. Y, argumentaría que las promesas de ES6 incluso faltan algunos de los conceptos básicos como la promisificación que son útiles en casi todos los proyectos de node.js.

Estoy más familiarizado con la biblioteca de promesa Bluebird, así que hablaré principalmente de mi experiencia con esa biblioteca.

Entonces, aquí están mis 6 razones principales para usar una biblioteca Promise más capaz

  1. Interfaces asíncronas no prometidas , .promisify()y .promisifyAll()son increíblemente útiles para manejar todas esas interfaces asíncronas que aún requieren devoluciones de llamada simples y aún no devuelven promesas: una línea de código crea una versión promisificada de una interfaz completa.

  2. Más rápido : Bluebird es significativamente más rápido que las promesas nativas en la mayoría de los entornos.

  3. Secuencia de iteración de matriz asíncrona , Promise.mapSeries()o le Promise.reduce()permite iterar a través de una matriz, llamando a una operación asincrónica en cada elemento, pero secuenciando las operaciones asincrónicas para que sucedan una tras otra, no todas al mismo tiempo. Puede hacerlo porque el servidor de destino lo requiere o porque necesita pasar un resultado al siguiente.

  4. Polyfill : si desea usar promesas en versiones anteriores de clientes de navegador, necesitará un polyfill de todos modos. También puede obtener un polyfill capaz. Dado que node.js tiene promesas de ES6, no necesita un polyfill en node.js, pero puede hacerlo en un navegador. Si está codificando tanto el servidor como el cliente de node.js, puede ser muy útil tener la misma biblioteca y características prometedoras en ambos (más fácil compartir código, cambio de contexto entre entornos, usar técnicas de codificación comunes para código asíncrono, etc.) .).

  5. Otras funciones de utilidad - Bluebird tiene Promise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each()y Promise.props()todos los cuales son en ocasiones muy útil. Si bien estas operaciones se pueden realizar con promesas de ES6 y código adicional, Bluebird viene con estas operaciones ya preconstruidas y probadas, por lo que es más simple y menos código usarlas.

  6. Advertencias integradas y rastros completos de la pila : Bluebird tiene una serie de advertencias integradas que lo alertan sobre problemas que probablemente sean código incorrecto o un error. Por ejemplo, si llama a una función que crea una nueva promesa dentro de un .then()controlador sin devolver esa promesa (para vincularla a la cadena de promesa actual), entonces, en la mayoría de los casos, es un error accidental y Bluebird le dará una advertencia al respecto efecto. Aquí se describen otras advertencias integradas de Bluebird .

Aquí hay más detalles sobre estos diversos temas:

Promisificar todos

En cualquier proyecto node.js, inmediatamente uso Bluebird en todas partes porque uso .promisifyAll()mucho en módulos node.js estándar como el fsmódulo.

Node.js no proporciona una interfaz prometedora para los módulos integrados que hacen asincrónica IO como fs módulo. Por lo tanto, si desea usar promesas con esas interfaces, puede codificar manualmente un envoltorio de promesas alrededor de cada función de módulo que use u obtener una biblioteca que pueda hacer eso por usted o no usar promesas.

Bluebird Promise.promisify()y Promise.promisifyAll()proporciona un ajuste automático de las API asíncronas de convención de llamadas de node.js para devolver promesas. Es extremadamente útil y ahorra tiempo. Lo uso todo el tiempo.

Aquí hay un ejemplo de cómo funciona:

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

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

La alternativa sería crear manualmente su propio envoltorio de promesa para cada fsAPI que desea utilizar:

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

Y, debe hacer esto manualmente para cada función de API que desee usar. Esto claramente no tiene sentido. Es el código repetitivo. También podría obtener una utilidad que haga esto por usted. Bluebird Promise.promisify()y Promise.promisifyAll()son una gran utilidad.

Otras características útiles

Estas son algunas de las características de Bluebird que específicamente encuentro útiles (hay un par de ejemplos de código a continuación sobre cómo pueden ahorrar código o acelerar el desarrollo):

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

Además de su útil función, Promise.map()también admite una opción de concurrencia que le permite especificar cuántas operaciones se deben permitir que se ejecuten al mismo tiempo, lo que es particularmente útil cuando tiene mucho que hacer, pero no puede abrumar a algunos fuera recurso.

Algunos de estos pueden llamarse independientes y usarse en una promesa que se resuelve en un iterable que puede ahorrar mucho código.


Polyfill

En un proyecto de navegador, dado que generalmente desea seguir siendo compatible con algunos navegadores que no tienen soporte Promise, terminará necesitando un polyfill de todos modos. Si también está utilizando jQuery, a veces puede usar el soporte de promesa integrado en jQuery (aunque en algunos aspectos es dolorosamente no estándar, tal vez arreglado en jQuery 3.0), pero si el proyecto involucra alguna actividad asíncrona significativa, encuentro Las características extendidas en Bluebird son muy útiles.


Más rápido

También vale la pena señalar que las promesas de Bluebird parecen ser significativamente más rápidas que las promesas integradas en V8. Vea esta publicación para más discusión sobre ese tema.


Falta una gran cosa Node.js

Lo que me haría considerar usar Bluebird menos en el desarrollo de node.js sería si node.js incorporara una función promisiva para que pudieras hacer algo como esto:

const fs = requirep('fs');

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

O simplemente ofrezca métodos ya prometidos como parte de los módulos integrados.

Hasta entonces, hago esto con Bluebird:

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

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

Parece un poco extraño tener el soporte de promesa ES6 integrado en node.js y que ninguno de los módulos incorporados devuelva promesas. Esto debe resolverse en node.js. Hasta entonces, uso Bluebird para promisificar bibliotecas completas. Por lo tanto, parece que las promesas se implementan aproximadamente en un 20% en node.js ahora, ya que ninguno de los módulos integrados le permite usar promesas con ellos sin envolverlos manualmente primero.


Ejemplos

Aquí hay un ejemplo de promesas simples frente a las promesas de Bluebird y Promise.map()para leer un conjunto de archivos en paralelo y notificar cuando haya terminado con todos los datos:

Promesas simples

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

Aquí hay un ejemplo de promesas simples frente a las promesas de Bluebird y Promise.map()cuando lee un montón de URL de un host remoto donde puede leer como máximo 4 a la vez, pero desea mantener tantas solicitudes en paralelo como sea permitido:

Promesas simples de JS

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
});
jfriend00
fuente
aunque es doloroso no estándar en algunos aspectos - Ellos afirman que son "Promesas / A + compatible" ahora :) - blog.jquery.com/2016/01/14/jquery-3-0-beta-released
thefourtheye
1
@thefourtheye: Sí, sé que han estado trabajando para lograr la compatibilidad Promise / A + en 3.0. Pero, eso todavía está en beta. Si cumple con la promesa (juego de palabras), puede obviar alguna de las razones para usar una biblioteca de promesa externa en el navegador JS si ya estaba usando jQuery. Todavía no tendrá todas las características útiles que tiene Bluebird y me sorprendería mucho si está a la altura del rendimiento de Bluebird, por lo que todavía hay espacio para Bluebird junto con un jQuery futuro en algunos casos. En cualquier caso, la pregunta del OP parece ser principalmente sobre node.js.
jfriend00
1
Hay un pequeño error en el último código de ejemplo: return new Promise(function(resolve, rejct). Debería ser:reject
Sebastian Muszyński
77
Node.js realmente tiene util.promisifyahora, aunque no hay un promisifyAllequivalente directo .
nyuszika7h
1
@Aurast: Sí, v11 se encarga fs, pero aún hay otras razones para usar Bluebird (mi favorito en particular es la concurrencyopción Promise.map()) para evitar abrumar a un servicio objetivo al que debes hacer un montón de solicitudes paralelas. Además, todavía hay muchas otras interfaces no prometidas para usar Bluebird's promisifyAll. Pero, lentamente, las razones para obtener Bluebird de inmediato en cada nuevo proyecto se desvanecen a medida que node.js refuerza su soporte de promesa incorporado.
jfriend00