现在我们有了ES6承诺,还有理由使用Q或BlueBird这样的承诺库吗?[关闭]


228

在Node.js添加对Promise的本机支持之后,还有理由使用Q或BlueBird之类的库吗?

例如,如果您正在启动一个新项目,并假设在该项目中您没有使用这些库的任何依赖项,那么我们是否可以说没有更多理由使用这些库了?


4
原生承诺具有非常非常基本的特征。Q或Bluebird之类的库增加了很多。如果需要这些功能,请使用这些库。
gman

7
我对标题进行了编辑,以减少对“需求”的了解,而对“仍然使用承诺库的原因”进行更多的讨论。这个问题可以通过主要提供事实而非观点来回答。应该重新打开它,因为它可以通过提供事实而不是主要是观点来回答。请参阅下面的答案作为说明。
jfriend00

11
@JaromandaX-现在请考虑重新打开标题和问题,以进一步了解为什么使用承诺库而不是是否“需要”使用承诺库。我认为,可以通过提供事实而不是主要是意见来回答这个问题-请参阅下面的答案以证明这一点。
jfriend00

6
标题编辑后的此问题及其接受的答案不是基于观点的。
最多

7
同意 以目前的形式,这是一个完全正确的问题。我已提名重新开放。
Jules

Answers:


367

古老的格言是,您应该选择适合该工作的工具。ES6承诺提供了基础知识。如果您只想了解或需要基础知识,那么对您来说应该/可以使用。但是,工具箱中不仅有基础知识,还有更多工具,并且在某些情况下,这些附加工具非常有用。而且,我认为ES6 Promise甚至缺失了Promisification等一些基本知识,这些基本知识在几乎每个node.js项目中都非常有用。

我对Bluebird Promise库最熟悉,因此我将主要根据我对该库的经验进行发言。

因此,这是我使用功能更强大的Promise库的6大理由

  1. 非Promisified异步接口 - .promisify()并且.promisifyAll()是非常有用的,以处理所有仍然需要简单的回调和尚未返回承诺的异步接口-一行代码创建整个界面的promisified版本。

  2. 更快 - 在大多数环境中,Bluebird的速度远远于本机承诺。

  3. 异步数组迭代的排序 - Promise.mapSeries()Promise.reduce()允许您遍历数组,在每个元素上调用异步操作,但是对异步操作进行排序,以便它们一个接一个地发生,而不是同时发生。您可以这样做是因为目标服务器需要它,或者因为您需要将一个结果传递给下一个。

  4. Polyfill-如果您想在较旧版本的浏览器客户端中使用Promise,则仍然需要使用polyfill。也可以得到一个功能强大的polyfill。由于node.js具有ES6 Promise,因此您不需要在node.js中进行polyfill,但是可以在浏览器中进行。如果您要同时对node.js服务器和客户端进行编码,则在两者中具有相同的promise库和功能可能会非常有用(易于共享代码,在环境之间进行上下文切换,对异步代码使用通用的编码技术等)。 )。

  5. 其它实用功能 -蓝鸟有Promise.map()Promise.some()Promise.any()Promise.filter()Promise.each()Promise.props()所有这一切都是偶然得心应手。虽然可以使用ES6 Promise和其他代码执行这些操作,但是Bluebird附带了已经预先构建和预先测试的这些操作,因此使用它们的代码更简单,代码更少。

  6. 内置警告和完整堆栈跟踪 -Bluebird具有许多内置警告,可以警告您可能是错误代码或错误的问题。例如,如果您调用一个在.then()处理程序中创建新诺言而不返回该诺言(将其链接到当前诺言链)的函数,则在大多数情况下,这是一个偶然的错误,Bluebird会向您发出警告影响。此处介绍了其他内置的Bluebird警告。

以下是有关这些各个主题的更多详细信息:

承诺全部

在任何node.js项目中,我都会随处使用Bluebird,因为我使用了 .promisifyAll()在标准的node.js模块(例如fs模块)上了很多。

Node.js本身并不向执行异步IO的内置模块(例如 fs 。因此,如果要在这些接口上使用promise,则可以手动编写所使用的每个模块函数的promise包装器代码,或者获取一个可以为您完成或不使用promise的库。

蓝鸟Promise.promisify()Promise.promisifyAll()提供的node.js调用约定异步API来回报承诺的自动包装。这非常有用且省时。我用它所有的时间。

这是一个如何工作的示例:

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

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

另一种方法是为fs您要使用的每个API 手动创建自己的Promise包装器:

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

并且,您必须为要使用的每个API函数手动执行此操作。这显然没有意义。这是样板代码。您不妨获得一个可以为您完成此工作的实用程序。蓝鸟Promise.promisify()Promise.promisifyAll()是这样的实用程序。

其他有用的功能

以下是一些我特别认为有用的Bluebird功能(下面有几个代码示例,说明这些功能如何节省代码或加快开发速度):

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

除了有用的功能 Promise.map()还支持并发选项,该选项可让您指定应允许同时运行多少个操作,这在您有很多事情要做但不能压倒其他事情时特别有用资源。

其中一些既可以称为独立应用,也可以用于承诺本身可以解析为可迭代程序的承诺,这样可以节省大量代码。


Polyfill

在浏览器项目中,由于您通常仍希望支持某些不支持Promise的浏览器,因此最终还是需要使用polyfill。如果您还使用jQuery,则有时可以只使用jQuery内置的Promise支持(尽管在某些方面很痛苦,它是非标准的,也许在jQuery 3.0中已修复),但是如果项目涉及任何重要的异步活动,我会发现Bluebird中的扩展功能非常有用。


快点

同样值得注意的是,Bluebird的承诺似乎比V8中内置的承诺要快得多。有关主题的更多讨论,请参见这篇文章


Node.js缺少了什么

让我考虑在node.js开发中减少使用Bluebird的原因是,如果node.js内置了promisify函数,那么您可以执行以下操作:

const fs = requirep('fs');

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

或者只是提供已经承诺的方法作为内置模块的一部分。

在此之前,我将使用Bluebird进行此操作:

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

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

将ES6 Promise支持内置到node.js中并且没有任何内置模块返回Promise似乎有点奇怪。这需要在node.js中解决。在那之前,我使用Bluebird来使整个库更加合理。因此,感觉诺言现在已经在node.js中实现了大约20%,因为没有内置模块允许您将诺言与它们一起使用,而无需先手动包装它们。


例子

这是一个简单的Promises与Bluebird的promisify的示例,Promise.map()用于并行读取一组文件并在处理完所有数据时通知:

简单的承诺

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

蓝鸟Promise.map()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
});

这是一个简单的Promises与Bluebird的Promisify的示例,并且Promise.map()当从远程主机读取一堆URL时,一次最多只能读取4个URL,但希望保持尽可能多的并行请求:

普通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();
    });
}

蓝鸟的承诺

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

尽管在某些方面非常不规范 -他们声称它们现在“兼容Promises / A +” :)- blog.jquery.com/2016/01/14/jquery-3-0-beta-released
thefourtheye

1
@thefourtheye-是的,我知道他们一直在努力实现3.0中的Promise / A +兼容性。但是,它仍处于测试阶段。如果它符合诺言(双关语),那么如果您已经在使用jQuery,则可以避免某些原因在浏览器JS中使用外部诺言库。它仍然不具备Bluebird所具有的所有有用功能,如果能够达到Bluebird的性能,我将感到非常惊讶,因此在某些情况下,Bluebird与未来的jQuery仍有空间。无论如何,OP的问题似乎主要与node.js有关。
jfriend00

1
在最后的示例代码中有一点错字:return new Promise(function(resolve, rejct)。应该是:reject
SebastianMuszyński17年

7
util.promisify尽管没有直接promisifyAll等效项,但Node.js实际上已经有了。
nyuszika7h

1
@Aurast-是的,v11可以解决问题fs,但还有其他一些原因要使用Bluebird(我最喜欢的是中的concurrency选项Promise.map()),以免压倒您需要向其发出大量并行请求的目标服务。另外,还有许多其他未承诺的接口可以与Bluebird的promisifyAll一起使用。但是,随着node.js自身增强其内置的Promise支持,逐渐在每个新项目中争夺Bluebird的原因逐渐消失。
jfriend00 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.