【问题标题】:Promised Connections Returning Nothing (JS)承诺的连接不返回任何内容 (JS)
【发布时间】:2014-05-16 22:35:10
【问题描述】:

承诺连接问题

我最近将我的 Node 应用程序从在我的本地计算机上运行转换为使用 Amazon EC2 来运行 Node 应用程序以及使用 VPN 来提供文件服务和 MySQL。

我对 Promises 的了解足以编写以下连接 sn-p(在响应客户端之前运行 3 个查询),使用 Bluebird。连接在我的机器上工作,但使用 VPN 托管的 MySQL 设置,连接每次都崩溃,大约在应用程序启动后 30 秒,我意识到这可能是因为我忘记关闭它们。

编辑:根据 cmets,看来问题不在于连接关闭。

所以我以我知道的最好的方式修改了我的脚本来关闭连接,但是对于 Promises,这很令人困惑。此版本的连接不起作用。 它不会失败或导致任何错误。它只是在服务器端不返回任何结果。我认为我的问题在于我关闭连接的方式。

  • 导致问题的原因是什么?

  • 是连接关闭吗?

  • 如果是这样,我将如何正确关闭它们?

我的(简化的)MySQL 连接尝试与Bluebird Promises

var mysql = require('mysql');
var Promise = require('bluebird');
var moment = require('moment');
function createConnection() {
    var connection = mysql.createConnection({
            dateStrings : true,
            host : 'hostname', 
            user : 'username',
            password : 'password', 
            database : 'database' 
        });
    connection = Promise.promisifyAll(connection);
    return connection;
}
function sendGame(req, res, sales, settings, categories, players) {
    var game = new Object();
    game.sales = sales;
    game.players = players;
    game.settings = settings;
    game.categories = categories;
    var JSONgame = JSON.stringify(game);
    console.log("Game: " + JSON.stringify(game, undefined, 4));
}
var retrieveSales = Promise.method(function (username, connection, timeFrame) {
        console.log('User ' + username + ' retrieving sales...');
        var q = 'select * from sales_entries where date BETWEEN ? AND ?';
        return connection.queryAsync(q, timeFrame).then(function (results) {
            return results[0];
        });
    });
var retrieveSettings = Promise.method(function (username, connection) {
        console.log('User ' + username + ' retrieving settings...');
        var q = 'select * from sales_settings';
        return connection.queryAsync(q).then(function (results) {
            return results[0];
        });
    });
var retrieveCategories = Promise.method(function (username, connection) {
        console.log('User ' + username + ' retrieving categories...');
        var q = 'select * from sales_categories';
        return connection.queryAsync(q).then(function (results) {
            return results[0];
        });
    });
var retrievePlayers = Promise.method(function (username, connection) {
        console.log('User ' + username + ' retrieving players...');
        var q = 'select * from users';
        return connection.queryAsync(q).then(function (results) {
            return results[0];
        });
    });
var gameSucceed = Promise.method(function gameSucceed(req, res) {
        var username = req.body.username;
        console.log('User ' + req.body.username + ' retrieving game...');
        var timeFrame = [moment().days(0).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss"), moment().days(6).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss")];
        //var connection = Promise.promisifyAll(createConnection());
        return connection.connectAsync().then(function () {
            console.log('Connection with the MySQL database openned for Game retrieval...');
            return Promise.all([retrieveSales(username, connection, timeFrame), retrieveSettings(username, connection), retrieveCategories(username, connection), retrievePlayers(username, connection)]);
        }).then(function () {
            connection.end(),
            console.log("...Connection with the MySQL database for Game retrieval ended")
        });
    });
function getGameData(req, res) {
    gameSucceed(req, res).spread(function (sales, settings, categories, players) {
        return sendGame(req, res, sales, settings, categories, players);
    });
};
var req = new Object();
var res = new Object();
req.body = {
    "username" : "user123",
    "password" : "password"
}
getGameData(req, res);

控制台结果

User user123 retrieving game...  
Connection with the MySQL database openned for Game retrieval...
User user123 retrieving sales... 
User user123 retrieving settings... 
User user123 retrieving categories... 
User user123 retrieving players... 
...Connection with the MySQL database for Game retrieval ended 
Game: {}

【问题讨论】:

  • 任何第二次查看这个问题的人,我都犯了一个错误,将我的 MySQL 连接详细信息公开。我已更改它们,但不希望通过编辑查看获得信息,因此我删除并重新发布了问题。
  • 你会同时使用两个 Promise 库吗?蓝鸟应该有你需要的一切。我的意思是,你甚至没有在任何地方使用Q
  • @Bergi 我对每个库的功能有些困惑。建议将两者中的一点作为解决我在应用程序的各个方面遇到的各种问题的解决方案。
  • 我可能在应用程序的不同位置使用 Q - 这只是一个片段。我将从问题中删除 Q 。 @Bergi
  • 实际上他们应该做同样的事情,afaik Bluebird 也提供了 Q 的所有方法。如果没有,请随时询问有关转换它的问题;即使他们应该一起玩得很好,只使用一个会更干净,而且不需要任何转换也不那么麻烦。

标签: javascript node.js promise q bluebird


【解决方案1】:
var gameSucceed = function gameSucceed(req, res) {
    …
    var connection = createConnection());
    return connection.connectAsync().then(function () {
        return Promise.all([…]);
    }).then(function () {
        connection.end();
    });
};

这个方法最终返回的promise没有解析值。它是由 then 调用创建的,您不会从其回调中调用 return - 这将导致 undefined。要解决此问题,只需通过以下方式路由结果:

.then(function(results) {
    connection.end();
    return results;
});

但是,如果您这样做,则不会在出现错误时关闭连接。最好的解决方案是使用finally() method,它就像同步代码中的finally 子句一样工作。它的回调将被调用,用于解决和拒绝,并且生成的承诺将自动进行该值。

.finally(function() {
    connection.end();
})
// .then(function(results) { })

【讨论】:

【解决方案2】:

您的代码有一个特定的资源管理问题,就像 Bergi 所说的那样。你必须记住什么时候关闭集合,什么时候不关闭。

最佳解决方案是使用Promise.using,但它仅在 Bluebird 的 v2 分支中可用,因此您将不得不等待一段时间。

在此之前,您可以创建自己的包装器方法来执行更基本的范围资源管理:

function connect(fn,timeout){
    timeout = (timeout === undefined) ? 8000 : timeout; // connection timeout
    return createConnection().then(function(connection){
        // run the function, when it resolves - close the connection
        // set a 7 second timeout on the connection
        return fn(connection).timeout(timeout).finally(function(){  
             connection.end();
        });
    });
}

这会让你做什么:

connect(function(connection){
    return gameSucceed(req,resp,connection); // connection is injected to that fn now
}).then(function(val){
      // gameSucceed resolution value here
});

现在,当gameSucceed 完成后,连接将自动关闭。这将使gameSucceed 本身看起来像:

var gameSucceed = Promise.method(function gameSucceed(req, res,connection) {
    var username = req.body.username;
    console.log('User ' + req.body.username + ' retrieving game...');
    var timeFrame = [moment().days(0).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss"), moment().days(6).hour(0).minute(0).second(0).format("YYYY-MM-DD HH:mm:ss")];
    return connection.connectAsync().then(function () {
        console.log('Connection with the MySQL database openned for Game retrieval...');
        return Promise.all([retrieveSales(username, connection, timeFrame), retrieveSettings(username, connection), retrieveCategories(username, connection), retrievePlayers(username, connection)]);
    }); // no longer its responsibility to handle the connection
});

通常,您可能还想为您的代码考虑一种更 OOPish 的编码风格。

祝你好运,编码愉快。

【讨论】:

  • 谢谢,我对第一个代码块有疑问。我不明白.timeout(timeout) 是如何工作的。我想我会理解,如果超时是一个函数而不是(timeout === undefined) ? 8000 : timeout - 所以我可以了解这一点,你能链接到关于(x == y) ? t : a 的任何文档吗?我从来没有使用过这种技术。
  • @jt0dd 当然,这是 Bluebird 特有的功能,如果超过 timeout 毫秒,它会自动拒绝承诺 - 这个想法是“掩饰你的背部”,这样如果你忘记拒绝或接受包含的承诺 - 你会很生气 TimeoutError 来通知你,这是个人风格 - 只有在没有该功能的情况下,删除它才会起作用。
  • 您谈论 promise 库的 v2 分支,就好像它是常识一样。我可以看到,对于一个熟练的 Web 开发人员来说,对下一个 JQuery 版本中即将发生的事情有所了解是很常见的,但是 Bluebird v2.x 呢?你是怎么知道的?
  • @jt0dd 我非常参与 Bluebird 的开发过程,我有一些拉取请求,并且我经常与主要库作者(Stack Overflow 中的@Esailija)和其他贡献者讨论它。首先,我是(现在仍然是)Bluebird 中Promise.using 的主要支持者之一,所以每当我看到对这种资源管理感兴趣的问题(这在某些语言中是非常基础的,但很难做到)有时自己做对,例如this)我有点呃……兴奋:)
  • 我真的很感谢您在球上的投入。保持。很酷的东西,从事这样的项目。很酷
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-07
  • 1970-01-01
  • 2021-05-04
  • 1970-01-01
  • 2018-02-15
  • 1970-01-01
  • 2012-07-17
相关资源
最近更新 更多