使用回调
这个页面解释了如何与现有的回调 api 和正在使用的库进行连接。我们将会看到,bluebird 使用回调 API 不仅很简单,而且还非常快。
我们将讨论一些主题。If you want to get the tl;dr what you need is likely the 使用 Node 约定与回调 API 一起工作 部分。
首先要确保我们在同一个页面上:
Promises 是有状态的,它们以等待的状态开始,并可以解决:
- fulfilled 也就是说,计算成功地完成了。
- rejected 这意味着计算失败了。
Promise 返回函数 不应该抛出,它们应该总是成功地返回一个在错误情况下被拒绝(rejected)的 promise。从 promise 返回函数抛出将迫使你使用 } catch {
和 一个 .catch
。 使用 promise化的 APIs 的人不期望 promises 会抛出。 如果您不确定异步 API 如何在 JS 中工作 - 请首先查看这个答案。
还有一个更常见的 StackOverflow 问题,关于如何将回调 API 转换成 promise。如果您在本指南中发现任何遗漏,请打开一个问题或拉请求。
自动 vs. 手动转换
有两种主要的方法可以将基于回调的 API 转换为基于 promise 的 API。您可以手动映射 API 调用到 promise 返回函数,或者您可以让 bluebird 为您做这件事。我们强烈推荐后者。
Promises 提供了很多非常酷和强大的保证,比如在手动转换 API 时很难提供安全保障。因此,只要有可能使用 Promise.promisify
和 Promise.promisifyAll
方法,我们推荐你使用它们。它们不仅是最安全的转换形式——它们还使用动态重新编译技术来引入很少的开销。
使用 Node 约定与回调 API 合作
在 Node/io.js 中,大多数 API 遵循一个约定 'error-first, single-parameter' 像这样:
function getStuff(data, callback) {
...
}
getStuff("dataParam", function(err, data) {
if (!err) {
}
});
这些 API 是 Node/io 使用的大多数核心模块,而 bluebird 采用了一种快速高效的方式将它们转换为基于 promise 的 API,通过基于承诺的api。promisify”和“的承诺。promisifyAll的函数调用。
Promise.promisify
—— 将一个回调函数转换成一个返回函数。它不会改变原来的函数并返回修改后的版本。将一个 单一的 回调函数转换为一个 promise 返回函数。它不会改变原来的函数,并返回修改后的版本。Promise.promisifyAll
—— 接受一个充满函数的 对象,并将 每个函数 转换为带有Async
后缀的新函数(默认情况下)。它并没有改变原来的函数,而是添加了新的函数。
注意 - 请检查链接文档以获得更多参数和使用示例.
这是一个 fs.readFile
的例子:
未使用 promise:
// callbacks
var fs = require("fs");
fs.readFile("name", "utf8", function(err, data) {
});
Promises:
var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("name", "utf8").then(function(data) {
});
注意,新方法是带有 Async
后缀,以 fs.readFileAsync
存在。它不会替换 fs.readFile
函数。单个函数也可以被 promise化:
var request = Promise.promisify(require("request"));
request("foo.bar").then(function(result) {
});
注意
Promise.promisify
和Promise.promisifyAll
使用动态重新编译来实现快速的包装,因此应该只调用它们一次。Promise.fromCallback
存在的情况下,这是不可能的。
使用一次性事件
有时候我们想知道一个单一的一次性事件何时完成。 例如 —— 一个流完成。为此我们可以使用 new Promise
。 请注意,只有在自动转换不可用时才应考虑此选项。
请注意,promises 会通过 time 对单个值进行建模,它们只解决一次——因此,当它们适合于单个事件时,不推荐将它们用于多事件 APIs。
例如,假设您有一个您想要绑定到的窗口 onload
事件。当窗口加载完成时,我们可以使用 promise 构造和解析:
// onload 示例, promise 构造函数接受一个
// 'resolver' 函数,这告诉 promise 这告诉 promise 什么时候去解决,并触发它的 `then` 处理程序。·
var loaded = new Promise(function(resolve, reject) {
window.addEventListener("load", resolve);
});
loaded.then(function() {
// window is loaded here
});
下面是另一个使用 API 的例子,它可以让我们知道什么时候连接就绪。这里的尝试是不完美的,我们很快就会解释为什么:
function connect() {
var connection = myConnector.getConnection(); // 同步.
return new Promise(function(resolve, reject) {
connection.on("ready", function() {
// 当连接建立起来时,把 promise 变为 fulfilled。
resolve(connection);
});
connection.on("error", function(e) {
// 如果连接错误, 把 promise 变为 rejected
reject(e); // e 最好是一个 `Error`.
});
});
}
上面的问题是 getConnection
本身可能会抛出一些原因,如果这样做,我们会得到一个同步拒绝。一个异步操作应该始终是异步的,以防止双重守护和竞争条件,所以最好始终将同步部分放在 promise 构造函数中,如下所示:
function connect() {
return new Promise(function(resolve, reject) {
// 如果 getConnection 在这里抛出,而不是得到一个异常,我们得将获得一个拒绝,从而产生一个更一致的 API。
var connection = myConnector.getConnection();
connection.on("ready", function() {
// 当连接建立起来时,把 promise 变为 fulfilled。
resolve(connection);
});
connection.on("error", function(e) {
// 如果连接错误, 把 promise 变为 rejected
reject(e); // e 最好是一个 `Error`.
});
});
}
使用延时
没有必要将超时/延迟转换为 bluebird API,bluebird 已经为这个用例提供了 Promise.delay
函数。请参阅文档中有关 timers
的使用和示例部分。
使用浏览器 APIs
通常,浏览器 API 是不标准的,自动的 promise化 将会失败。如果您正在使用一个 API,您无法通过 promisify
和 promisifyAll
来进行 promise化,请参考 使用其他 APIs 部分。
使用 databases
对于一般资源管理和特别是数据库,bluebird 包括强大的 Promise.using
和处置器(disposers)系统。 这与 Python 中的 with
、C#中的 using
、Java 中的 try/resource和C++ 中的 RAII 类似,它使您可以自动处理资源管理。
数据库的几个例子如下:
注意 更多的例子请参阅
Promise.using
部分。
Mongoose/MongoDB
Mongoose 使用永久连接工作,驱动器负责重新连接/处置。出于这个原因,使用 using
并不是必需的 —— 而是在服务器启动时连接并使用 promise化 来暴露 promise。
请注意,Mongoose 已经提供了 promise 支持,但它提供的承诺明显较慢,不报告未处理的拒绝,所以无论如何建议使用自动的 promise化:
var Mongoose = Promise.promisifyAll(require("mongoose"));
Sequelize
Sequelize 内部已经使用了 Bluebird 的 promise,并具有返回 promise api。使用它们。
RethinkDB
RethinkDB 内部已经使用了 Bluebird 的 promise,并具有返回 promise api。使用它们。
Bookshelf
Bookshelf 内部已经使用了 Bluebird 的 promise,并具有返回 promise api。使用它们。
PostgreSQL
下面是如何为 PostgreSQL 驱动程序创建一个处置器:
var pg = require("pg");
// Uncomment if pg has not been properly promisified yet.
//var Promise = require("bluebird");
//Promise.promisifyAll(pg, {
// filter: function(methodName) {
// return methodName === "connect"
// },
// multiArgs: true
//});
// Promisify rest of pg normally.
//Promise.promisifyAll(pg);
function getSqlConnection(connectionString) {
var close;
return pg.connectAsync(connectionString).spread(function(client, done) {
close = done;
return client;
}).disposer(function() {
if (close) close();
});
}
module.exports = getSqlConnection;
Which would allow you to use:
var using = Promise.using;
using(getSqlConnection(), function(conn) {
// use connection here and _return the promise_
}).then(function(result) {
// connection already disposed here
});
也可以为事物管理使用处置器模式 (但不是实际的 .disposer):
function withTransaction(fn) {
return Promise.using(pool.acquireConnection(), function(connection) {
var tx = connection.beginTransaction()
return Promise
.try(fn, tx)
.then(function(res) { return connection.commit().thenReturn(res) },
function(err) {
return connection.rollback()
.catch(function(e) {/* maybe add the rollback error to err */})
.thenThrow(err);
});
});
}
exports.withTransaction = withTransaction;
你可以这样做:
withTransaction(tx => {
return tx.queryAsync(...).then(function() {
return tx.queryAsync(...)
}).then(function() {
return tx.queryAsync(...)
});
});
MySQL
下面是如何为 MySQL 驱动程序创建一个处置器:
var mysql = require("mysql");
// 如果 mysql 还没有被正确的 promise化,就取消注释
// var Promise = require("bluebird");
// Promise.promisifyAll(mysql);
// Promise.promisifyAll(require("mysql/lib/Connection").prototype);
// Promise.promisifyAll(require("mysql/lib/Pool").prototype);
var pool = mysql.createPool({
connectionLimit: 10,
host: 'example.org',
user: 'bob',
password: 'secret'
});
function getSqlConnection() {
return pool.getConnectionAsync().disposer(function(connection) {
connection.release();
});
}
module.exports = getSqlConnection;
使用模式类似于上面的 PostgreSQL 示例。您也可以使用 disposer 模式(但不是实际 .disposer)。请参阅上面的 PostgreSQ L示例,以获得指导。
更常见的例子
一些将上述实践应用于一些流行的库的例子:
// 最受欢迎的 redis 模块
var Promise = require("bluebird");
Promise.promisifyAll(require("redis"));
// 最受欢迎的 mongodb 模块
var Promise = require("bluebird");
Promise.promisifyAll(require("mongodb"));
// 最受欢迎的 mysql 模块
var Promise = require("bluebird");
// Note that the library's classes are not properties of the main export
// so we require and promisifyAll them manually
Promise.promisifyAll(require("mysql/lib/Connection").prototype);
Promise.promisifyAll(require("mysql/lib/Pool").prototype);
// Mongoose
var Promise = require("bluebird");
Promise.promisifyAll(require("mongoose"));
// Request
var Promise = require("bluebird");
Promise.promisifyAll(require("request"));
// Use request.getAsync(...) not request(..), it will not return a promise
// mkdir
var Promise = require("bluebird");
Promise.promisifyAll(require("mkdirp"));
// Use mkdirp.mkdirpAsync not mkdirp(..), it will not return a promise
// winston
var Promise = require("bluebird");
Promise.promisifyAll(require("winston"));
// rimraf
var Promise = require("bluebird");
// The module isn't promisified but the function returned is
var rimrafAsync = Promise.promisify(require("rimraf"));
// xml2js
var Promise = require("bluebird");
Promise.promisifyAll(require("xml2js"));
// jsdom
var Promise = require("bluebird");
Promise.promisifyAll(require("jsdom"));
// fs-extra
var Promise = require("bluebird");
Promise.promisifyAll(require("fs-extra"));
// prompt
var Promise = require("bluebird");
Promise.promisifyAll(require("prompt"));
// Nodemailer
var Promise = require("bluebird");
Promise.promisifyAll(require("nodemailer"));
// ncp
var Promise = require("bluebird");
Promise.promisifyAll(require("ncp"));
// pg
var Promise = require("bluebird");
Promise.promisifyAll(require("pg"));
在上述所有情况下,这个库都以这样或那样的方式使其类可用。如果不是这样,您仍然可以通过创建一个一次性的实例来进行 promise化:
var ParanoidLib = require("...");
var throwAwayInstance = ParanoidLib.createInstance();
Promise.promisifyAll(Object.getPrototypeOf(throwAwayInstance));
// Like before, from this point on, all new instances + even the throwAwayInstance suddenly support promises
与其他 API 一起工作
有时,您必须使用不一致的 API,而不遵循惯例。
注意 Promise 返回函数永远不会抛出
例如,类似:
function getUserData(userId, onLoad, onFail) { ...}
我们可以使用 promise 构造函数将其转换为 promise 返回函数:
function getUserDataAsync(userId) {
return new Promise(function(resolve, reject) {
// Put all your code here, this section is throw-safe.
getUserData(userId, resolve, reject);
});
}