既然我们有 ES6 Promise,还有理由使用像 Q 或 BlueBird 这样的Promise库吗?

IT技术 javascript node.js promise q bluebird
2021-02-08 13:33:11

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

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

1个回答

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

我最熟悉Bluebird promise 库,所以我将主要根据我对该库的经验进行发言。

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

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

  2. 更快-在大多数环境中,Bluebird比原生Promise要快得多

  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()处理程序中创建新Promise的函数而不返回该Promise(将其链接到当前的Promise链),那么在大多数情况下,这是一个意外错误,Bluebird 会向您发出警告影响。此处描述了其他内置的 Bluebird 警告

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

全部Promise

在任何 node.js 项目中,我都会立即在任何地方使用 Bluebird,因为我.promisifyAll()在标准 node.js module(如fsmodule)使用了很多

Node.js 本身并不像module那样为执行异步 IO 的内置module提供 promise 接口fs所以,如果你想在这些接口中使用 promise,你要么手工编写一个围绕你使用的module函数的 promise 包装器,要么得到一个可以为你做到这一点的库,或者不使用 promise。

BluebirdPromise.promisify()Promise.promisifyAll()提供自动包装 node.js 调用约定异步 API 以返回Promise。这是非常有用和节省时间的。我用它所有的时间。

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

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'sPromise.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 的 promise 似乎比 V8 中内置的 promise 快得多。有关主题的更多讨论,请参阅此帖子


Node.js 缺失的一件大事

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

const fs = requirep('fs');

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

或者只是提供已经Promise的方法作为内置module的一部分。

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


例子

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

简单的Promise

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

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

简单的 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
});
最后一个示例代码中有一个小错误:return new Promise(function(resolve, rejct). 应该:reject
2021-03-18 13:33:11
@thefourtheye - 是的,我知道他们一直在努力实现 3.0 中的 Promise/A+ 兼容性。但是,这仍处于测试阶段。如果它符合Promise(双关语),如果您已经在使用 jQuery,那么它可能会消除在浏览器 JS 中使用外部Promise库的一些原因。它仍然没有 Bluebird 的所有有用功能,如果它达到 Bluebird 的性能,我会感到非常惊讶,因此在某些情况下,Bluebird 与未来的 jQuery 一起仍有空间。无论如何,OP 的问题似乎主要是关于 node.js。
2021-03-19 13:33:11
虽然它在某些方面非常不标准- 他们声称他们现在是“Promises/A+ 兼容的”:) - blog.jquery.com/2016/01/14/jquery-3-0-beta-released
2021-03-28 13:33:11
Node.jsutil.promisify现在实际上已经有了,尽管没有直接的promisifyAll等价物。
2021-03-31 13:33:11
@Aurast - 是的,v11 处理了fs,但还有一些其他原因使用 Bluebird(我特别喜欢的是 中的concurrency选项Promise.map())来避免压倒您需要向其发出一堆并行请求的目标服务。此外,还有很多其他非Promise接口可以与 Bluebird 的 promisifyAll 一起使用。但是,随着 node.js 本身加强其内置的 promise 支持,在每个新项目中立即获取 Bluebird 的理由正在逐渐消失。
2021-04-02 13:33:11