【问题标题】:Are there still reasons to use promise libraries like Q or BlueBird now that we have ES6 promises? [closed]既然我们有了 ES6 承诺,还有理由使用 Q 或 BlueBird 之类的承诺库吗? [关闭]
【发布时间】:2016-04-29 21:22:06
【问题描述】:

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

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

【问题讨论】:

  • 原生承诺具有非常非常基本的特性。 Q 或 Bluebird 等库增加了更多。如果您需要这些功能,请使用这些库。
  • 我编辑了标题以减少“需要”,而更多地关注“仍然使用承诺库的原因”。这个问题可以通过主要提供事实而不是意见来回答。应该重新打开它,因为它可以通过提供事实而不是主要意见来回答。请参阅下面的答案作为演示。
  • @JaromandaX - 请考虑重新打开,因为标题和问题已经过调整,以更多地说明为什么要使用承诺库,而不是是否“需要”使用承诺库。在我看来,这个问题可以通过提供事实而不是主要意见来回答 - 请参阅下面的答案作为证明。
  • 标题编辑后的这个问题及其接受的答案不是基于意见的。
  • 同意。这是当前形式的一个完全有效的问题。我已提名重新开放。

标签: javascript node.js promise q bluebird


【解决方案1】:

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

我对@9​​87654321@ 最为熟悉,所以我将主要根据我使用该库的经验发言。

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

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

  2. 更快 - 在大多数环境中,Bluebird 比原生承诺 significantly faster

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

  4. Polyfill - 如果你想在旧版本的浏览器客户端中使用 Promise,无论如何你都需要一个 polyfill。也可以得到一个有能力的polyfill。由于 node.js 有 ES6 承诺,你不需要在 node.js 中使用 polyfill,但你可以在浏览器中使用。如果您同时编写 node.js 服务器和客户端,那么在两者中拥有相同的 Promise 库和功能可能非常有用(更易于共享代码、环境之间的上下文切换、对异步代码使用通用编码技术等。 .).

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

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

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

PromisifyAll

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

Node.js 本身并不为像 fs 模块那样执行异步 IO 的内置模块提供 Promise 接口。所以,如果你想在这些接口上使用 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 手动创建自己的 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。


Polyfill

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


更快

另外值得注意的是,Bluebird 的 Promise 似乎比 V8 中内置的 Promise 快得多。有关该主题的更多讨论,请参阅this post


缺少一个重要的 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
});

在 node.js 中内置 ES6 Promise 支持并且没有任何内置模块返回 Promise 似乎有点奇怪。这需要在 node.js 中解决。在那之前,我使用 Bluebird 来承诺整个库。因此,感觉现在 node.js 中实现了大约 20% 的 Promise,因为没有一个内置模块允许您在不先手动包装它们的情况下使用 Promise。


示例

这是一个简单的 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 个,但希望在允许的范围内保持尽可能多的并行请求:

纯 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 - 是的,我知道他们一直致力于在 3.0 中实现 Promise/A+ 兼容性。但是,这仍处于测试阶段。如果它符合承诺(双关语),如果您已经在使用 jQuery,它可能会避免在浏览器 JS 中使用外部承诺库的某些原因。它仍然不具备 Bluebird 所做的所有有用功能,如果它达到 Bluebird 的性能,我会感到非常惊讶,因此在某些情况下,Bluebird 与未来的 jQuery 一起仍有空间。无论如何,OP 的问题似乎主要是关于 node.js。
  • 最后一个示例代码中有一个小错字:return new Promise(function(resolve, rejct)。应该是:reject
  • Node.js 现在实际上有 util.promisify,虽然没有直接的 promisifyAll 等价物。
  • @Aurast - 是的,v11 处理了fs,但还有一些使用 Bluebird 的其他原因(我特别喜欢的是 Promise.map() 中的 concurrency 选项)来防止压倒目标您需要向其发出一堆并行请求的服务。此外,还有很多其他未承诺的接口可以使用 Bluebird 的 promisifyAll。但是,随着 node.js 本身加强其内置的 Promise 支持,在每个新项目中立即使用 Bluebird 的理由逐渐消失。
猜你喜欢
  • 2016-07-27
  • 2015-04-15
  • 2015-01-24
  • 2017-06-24
  • 2013-01-06
  • 2018-11-03
  • 1970-01-01
  • 2015-05-12
  • 2014-05-06
相关资源
最近更新 更多