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

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


免责声明:此信息已过时。蓝鸟Github回购有以下说明

如果可能,请使用本地承诺代替。原生承诺已经在Node.js和浏览器中稳定了大约6年,并且已经快速了大约3年。蓝鸟仍然提供了一些有用的实用程序方法,你可以使用它-但请先考虑本地承诺。

这是一件好事,致力于Bluebird和承诺的人们已经能够帮助将Bluebird中大多数有用的东西整合到JavaScript本身和平台/引擎中。还有一些东西(.map/。过滤器正在与迭代助手提案和异步迭代器一起前进!)。

如果有一个功能让你一直使用蓝鸟。请让我们知道,这样我们就可以尝试上游了:)

目前-如果你需要支持旧的浏览器或EoL Node.js,或者作为使用警告/监控来发现错误的中间步骤,只建议使用Bluebird。

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

我最熟悉的是蓝鸟承诺库,所以我将主要从我使用该库的经验中发言。

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

Non-Promisified async interfaces - .promisify() and .promisifyAll() are incredibly useful to handle all those async interfaces that still require plain callbacks and don't yet return promises - one line of code creates a promisified version of an entire interface. Faster - Bluebird is significantly faster than native promises in most environments. Sequencing of async array iteration - Promise.mapSeries() or Promise.reduce() allow you to iterate through an array, calling an async operation on each element, but sequencing the async operations so they happen one after another, not all at the same time. You can do this either because the destination server requires it or because you need to pass one result to the next. Polyfill - If you want to use promises in older versions of browser clients, you will need a polyfill anyway. May as well get a capable polyfill. Since node.js has ES6 promises, you don't need a polyfill in node.js, but you may in a browser. If you're coding both node.js server and client, it may be very useful to have the same promise library and features in both (easier to share code, context switch between environments, use common coding techniques for async code, etc...). Other Useful Features - Bluebird has Promise.map(), Promise.some(), Promise.any(), Promise.filter(), Promise.each() and Promise.props() all of which are occasionally handy. While these operations can be performed with ES6 promises and additional code, Bluebird comes with these operations already pre-built and pre-tested so it's simpler and less code to use them. Built in Warnings and Full Stack Traces - Bluebird has a number of built in warnings that alert you to issues that are probably wrong code or a bug. For example, if you call a function that creates a new promise inside a .then() handler without returning that promise (to link it into the current promise chain), then in most cases, that is an accidental bug and Bluebird will give you a warning to that effect. Other built-in Bluebird warnings are described here.

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

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

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