这个页面解释了如何与现有的回调 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.promisifyPromise.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.promisifyPromise.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,您无法通过 promisifypromisifyAll 来进行 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);
    });
}