现在我们已经有了ES6的Promise,还有使用Q或BlueBird等Promise库的理由吗?

253
在Node.js原生支持Promise之后,是否仍有使用Q或BlueBird等库的理由?
例如,如果您正在启动一个新项目,并且假设在此项目中您没有使用这些库的任何依赖项,那么我们可以说真的没有使用这些库的理由了吗?

5
原文:Native promises have very very basic features. Libraries like Q or Bluebird add a bunch more. If you need those features then use those libraries.翻译:原生的 Promise 功能非常基础,像 Q 或 Bluebird 这样的库提供了更多功能。如果你需要这些功能,就使用这些库。 - gman
8
我将标题进行了编辑,使其不再强调“需要”,而更多地关注“仍然使用承诺库的原因”。本问题的答案主要应以事实为基础,而非观点。它应该被重新打开,因为它可以通过提供事实而不是主要观点来回答。请参考下面的答案作为示范。 - jfriend00
13
@JaromandaX - 请考虑重新开放问题,因为标题和问题已经被调整为更多地关于为什么要使用Promise库,而不是是否“需要”使用Promise库。在我看来,这个问题可以通过提供事实而不是主观意见来回答 - 可以参考下面的答案作为示范。 - jfriend00
7
这个问题在标题修改后,以及它被接受的答案,并非基于观点。 - max
8
同意,这个问题的当前形式是完全有效的。我已经提名要重新开放。 - Jules
因为一些工程师喜欢在项目中尽可能增加学习曲线和API表面积,而实际上我们可以通过使用每个人都能理解的原生Promise来解决各种情况(尤其是现在原生已经支持了allSettled等功能)。此外,他们还想将项目大小增加75kb(类似于Bluebird的体积)。 - Dominic
1个回答

403
免责声明:此信息已过时。 Bluebird Github仓库中有以下注释:

如果可能,请使用本机Promise。 本机Promise在Node.js和浏览器中已稳定使用了约6年,并且它们大约3年快速。 Bluebird仍然提供一些有用的实用程序方法,您可以使用它 - 但请首先考虑使用本机Promise。

这是一件好事,从事Bluebird和Promise工作的人员已经能够帮助将Bluebird的大部分有用功能合并到JavaScript本身和平台/引擎中。仍存在一些缺失之处(.map / .filter随迭代助手提案和异步迭代器一起出现!)。

如果有一个功能使您继续使用bluebird。请告诉我们,以便我们可以尝试上游它:)

当前-只建议在需要支持旧浏览器或EoL Node.js或作为使用警告/监控查找错误的中间步骤时使用Bluebird。

古老的格言是应该为工作选择正确的工具。ES6 Promise提供基础知识。如果你所想要或需要的一切只是基础,那么这应该/可能可以为你正常工作。但是,工具箱中有更多的工具而不仅仅是基础,有些情况下这些附加工具非常有用。而且,我认为ES6 Promise甚至缺少了一些像Promisification这样的基本功能,在几乎每个node.js项目中都很有用。

我最熟悉的是Bluebird promise库,所以我将主要从我的经验谈论该库。

因此,以下是我使用更强大的Promise库的前6个原因

  1. 未Promisified的异步接口 - .promisify().promisifyAll()非常有用,用于处理所有这些异步接口,这些接口仍需要普通回调并且尚未返回承诺-一行代码就创建了整个接口的Promisified版本。

  2. 更快 -在大多数环境中,Bluebird比本机promise显着更快

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

  4. Polyfill - 如果你想在旧版的浏览器客户端中使用promises,你将需要polyfill。最好选择一个功能强大的polyfill。由于node.js具有ES6 promises,因此在node.js中不需要polyfill,但是在浏览器中可能需要。如果你同时编写node.js服务器和客户端代码,则在两者中使用相同的promise库和特性非常有用(更容易共享代码,在环境之间切换上下文,并使用异步代码的通用编程技术等)。

  5. 其他有用的特性 - Bluebird拥有Promise.map()Promise.some()Promise.any()Promise.filter()Promise.each()Promise.props()等其他有用的特性。虽然可以通过ES6 promises和附加代码执行这些操作,但Bluebird已经预先构建和测试了这些操作,因此使用它们更简单且代码更少。

  6. 内置警告和完整堆栈跟踪 - Bluebird具有许多内置警告,可警告您可能是错误代码或bug的问题。例如,如果在.then()处理程序中调用创建新promise的函数而没有返回该promise(将其链接到当前promise链中),则在大多数情况下,这是一个意外的bug,Bluebird会给您一个警告。其他内置的Bluebird警告在此处描述。

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

PromisifyAll

在任何node.js项目中,我立即在所有地方使用Bluebird,因为我经常在标准的node.js模块(如fs模块)上使用.promisifyAll()

Node.js本身不提供承诺接口来处理像fs模块这样的异步IO执行内置模块。因此,如果你想使用这些接口来处理承诺,你可以手动编写一个promise包装器来处理每个模块函数,或者获取可以为你完成这项工作的库,或者不使用promises。

Bluebird的Promise.promisify()Promise.promisifyAll()提供了一个自动包装node.js调用约定异步API来返回promises。这非常有用且省时。我一直都在使用它。

以下是其工作方式的示例:

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函数执行此操作,这显然没有意义。这是样板代码。你可能想要获取一个可以帮助你完成这项工作的实用程序。Bluebird的Promise.promisify()Promise.promisifyAll()正是这样的实用程序。

其他有用的功能

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

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

Promise.map() 不仅具有有用的功能,还支持并发选项,让您指定同时运行多少个操作,这在您有很多事情要做但不能压倒某些外部资源时特别有用。

其中一些可以独立调用并用于解析为可迭代对象的 Promise 上,这可以节省大量代码。


填充

在浏览器项目中,由于通常需要支持一些不支持 Promise 的浏览器,因此您最终仍然需要一个 polyfill。如果您还使用 jQuery,有时可以直接使用内置到 jQuery 中的 promise 支持(尽管在某些方面非常不标准,可能在 jQuery 3.0 中得到修复),但是如果项目涉及任何重要的异步活动,则发现 Bluebird 中的扩展功能非常有用。


更快

值得注意的是,Bluebird 的 promises 看起来比 V8 中内置的 promises 显着更快。有关该主题的更多讨论,请参见此帖子


Node.js 缺失的重要功能

如果 Node.js 内置一个 promisify 函数,您可以像这样使用它来考虑在 Node.js 开发中使用 Bluebird 的情况:

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

在node.js中内置了ES6 Promise支持,但是没有内置模块返回Promise似乎有点奇怪。这需要在node.js中得到解决。在此之前,我使用Bluebird将整个库promisify化。因此,感觉现在node.js中只实现了20%的Promise功能,因为没有内置模块让你在没有手动包装它们的情况下使用Promise。


示例

以下是使用原始Promises与Bluebird的promisify和Promise.map()来并行读取一组文件并在完成所有数据后通知的示例:

原始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()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
});

以下是使用普通Promise和Bluebird的promisify和Promise.map()从远程主机读取大量URL时的示例,其中您最多可以同时读取4个URL,但希望尽可能并行地保持尽可能多的请求:

普通JS Promise

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

蓝鸟 Promise

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+兼容” :) - http://blog.jquery.com/2016/01/14/jquery-3-0-beta-released/ - thefourtheye
1
@thefourtheye - 是的,我知道他们一直在努力使3.0兼容Promise/A+。但是,那仍然处于测试版阶段。如果它能够实现承诺(双关语),那么如果您已经在使用jQuery,则可能取消了在浏览器JS中使用外部promise库的某些原因。它仍然没有Bluebird具有的所有有用功能,我会非常惊讶如果它能够达到Bluebird的性能,因此在某些情况下,Bluebird仍然可以与未来的jQuery并存。无论如何,OP的问题似乎主要是关于node.js的。 - jfriend00
1
在最后一个示例代码中有一个小错别字:return new Promise(function(resolve, rejct)。应该是:reject - Sebastian Muszyński
9
现在Node.js实际上已经有了util.promisify,尽管没有直接对应的promisifyAll - alexia
1
@Aurast - 是的,v11已经处理了fs,但仍有其他原因使用Bluebird(我特别喜欢在Promise.map()中使用concurrency选项),以避免对需要进行大量并行请求的目标服务造成压力。此外,仍有许多未经promisifyAll的接口可用于Bluebird。但是,随着node.js本身加强其内置的promise支持,立即在每个新项目中抓取Bluebird的理由正在逐渐减少。 - jfriend00
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接