既然我们有了ES6承诺,还有理由使用Q或BlueBird之类的承诺库吗?

在Node.js增加了对承诺的本地支持之后,还有理由使用Q或BlueBird这样的库吗?

例如,如果您正在开始一个新项目,并且假设在这个项目中您没有使用这些库的任何依赖关系,我们是否可以说真的没有更多的理由使用这些库?

67694 次浏览

老话说,你应该为工作选择正确的工具。ES6承诺提供基础。如果你想要或需要的只是基本的东西,那么它应该/可以为你工作。但是,除了基本的工具之外,还有更多的工具,在某些情况下,这些额外的工具非常有用。而且,我认为ES6的承诺甚至缺少了一些基本的东西,比如承诺,这些东西在几乎每个node.js项目中都很有用。

我最熟悉的蓝鸟承诺图书馆,所以我将主要从我的经验与该库。

因此,以下是我使用更强大的Promise库的6大理由

  1. .promisify().promisifyAll()对于处理所有那些仍然需要普通回调且还没有返回承诺的异步接口非常有用——一行代码就创建了整个接口的承诺版本。

  2. 更快 -蓝鸟是明显快于大多数环境中的本地承诺

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

  4. Polyfill -如果你想在旧版本的浏览器客户端中使用promise,你无论如何都需要一个Polyfill。还不如找个有能力的填充物。因为node.js有ES6承诺,所以在node.js中你不需要polyfill,但在浏览器中你可能需要。如果你同时对node.js服务器端和客户端进行编码,那么在两者中使用相同的承诺库和特性(更容易共享代码,在环境之间切换上下文,为异步代码使用通用编码技术等)可能会非常有用。

  5. 其他有用的功能 -蓝鸟有Promise.map()Promise.some()Promise.any()Promise.filter()Promise.each()Promise.props(),这些偶尔都很方便。虽然这些操作可以通过ES6承诺和额外的代码来执行,但Bluebird已经预先构建和测试了这些操作,因此使用它们更简单,代码更少。

  6. 内置警告和全堆栈跟踪 -蓝鸟有许多内置警告,提醒您可能是错误的代码或错误的问题。例如,如果你调用了一个函数,该函数在.then()处理程序中创建了一个新的承诺,而没有返回该承诺(将其链接到当前的承诺链中),那么在大多数情况下,这是一个意外错误,Bluebird会给你一个警告。其他内置的蓝鸟警告是

以下是关于这些不同主题的更多细节:

PromisifyAll

在任何node.js项目中,我都会立即在任何地方使用Bluebird,因为我在标准node.js模块(如fs模块)上大量使用.promisifyAll()

Node.js本身并没有为内置模块提供一个承诺接口,这些模块像fs模块一样执行异步IO。因此,如果你想在这些接口上使用promise,你就只能手工编写一个promise包装器来包装你使用的每个模块函数,或者获取一个可以为你做这件事的库,或者不使用promise。

Bluebird的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创建自己的承诺包装器:

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函数手动执行此操作。这显然说不通。这是样板代码。您还可以使用一个实用程序来完成这项工作。Bluebird的Promise.promisify()Promise.promisifyAll()就是这样一个实用程序。

其他有用功能

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

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

除了它的有用的功能,Promise.map()还支持一个并发选项,让你指定多少操作应该被允许同时运行,这是特别有用的,当你有很多事情要做,但不能压倒一些外部资源。

其中一些既可以称为独立的,也可以用于本身解析为可迭代对象的promise,这可以节省大量代码。


Polyfill

在浏览器项目中,由于您通常希望仍然支持一些没有Promise支持的浏览器,因此无论如何最终都需要一个polyfill。如果你也在使用jQuery,你有时可以只使用jQuery内置的承诺支持(尽管它在某些方面是令人痛苦的非标准的,可能在jQuery 3.0中得到了修复),但如果项目涉及任何重要的异步活动,我发现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
});

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

在那之前,我对蓝鸟是这样做的:

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


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

在node.js中内置了ES6承诺支持,而没有任何内置模块返回承诺,这似乎有点奇怪。这需要在node.js中进行整理。在那之前,我用蓝鸟来承诺整个图书馆。所以,现在node.js中实现了大约20%的承诺,因为没有一个内置模块允许你在不手动包装它们的情况下使用承诺。


例子

下面是一个plain Promises vs. 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
});

下面是一个plain Promises vs. Bluebird的promisify和Promise.map()的例子,当你从远程主机读取一堆url时,你一次最多可以读取4个,但想要保持尽可能多的并行请求:

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