0

Problem with Promised Connections

I recently converted my Node app from running on my local machine to utilizing an Amazon EC2 for the Node app and a VPN for the file-serving and MySQL.

I learned just enough about Promises to write the following connection snippet (which runs 3 queries before responding to the client), utilizing Bluebird. The connections worked on my machine, but with the VPN hosted MySQL settings, the connections crashed every time, about 30 seconds after the app started, which I realized was probably because I'd forgotten to close them.

EDIT: Based on the comments, it appears the issue is not in the connection closures.

So I modified my script in the best way I knew to close the connections, but with Promises, this is confusing. This version of the connection doesn't work. It doesn't fail or cause any errors. It just returns no results on the server side. I think my problem is in the way I've closed the connections.

  • What's causing the issue?

  • Is it the connection closures?

  • If so, how would I close them properly?

My (Simplified) MySQL Connection Attempt with 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);

Console Result

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: {}
  • 1
    Anyone who's looking at this question for a second time, I made the mistake of leaving my MySQL connection details out in the open. I've changed them, but didn't want the information available via edit viewing, so I deleted and re-posted the question. –  May 16 '14 at 02:42
  • 2
    WTH do you use two promise libraries together? Bluebird should have all that you need. I mean, you're not even using `Q` anywhere? – Bergi May 16 '14 at 02:57
  • @Bergi I'm a little confused between what each library does. A little bit of both was suggested as solutions to various problems I've run into working on the various aspects of the app. –  May 16 '14 at 03:02
  • I might be using Q at a different location in the app - this is just a piece. I'll remove Q from the question. @Bergi –  May 16 '14 at 03:04
  • 1
    Actually they should do the same, and afaik Bluebird offers all the methods that Q has as well. If not, feel free to ask a question about converting it; even if they should play together well it would be cleaner to use only one and less cumbersome not needing any conversions. – Bergi May 16 '14 at 03:06
  • I assume that `JSONgame` misses a `var` and that you call `Promise.promisifyAll()` on the connection twice are only relicts of your simplification? If so, please fix them. – Bergi May 16 '14 at 03:08
  • @Bergi done. So I'm forgetting to return from the callbacks... I'm having a look at that –  May 16 '14 at 03:15
  • I hate asking question like this, because even with all the simplification I can do, they still fail to demonstrate a fundamental mistake that will likely help other viewers in the future. –  May 16 '14 at 03:21
  • @jt0dd: No problem, I've highlighted the faulty part in my answer :-) – Bergi May 16 '14 at 03:22
  • @Bergi thanks so much. It was too much code really. Thanks for your time. –  May 16 '14 at 03:24

2 Answers2

1
var gameSucceed = function gameSucceed(req, res) {
    …
    var connection = createConnection());
    return connection.connectAsync().then(function () {
        return Promise.all([…]);
    }).then(function () {
        connection.end();
    });
};

The promise that is ultimately returned from this method does not have a resolution value. It is created by that then call from whose callback you do not return - which will lead to undefined. To fix this, just route the result through:

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

However, if you do it like that the connection won't be closed in case of an error. The best solution is to use the finally() method, which just works like a finally clause in synchronous code. It's callback will be invoked both for resolutions and rejections, and the resulting promise will automatically carry on the value.

.finally(function() {
    connection.end();
})
// .then(function(results) { })
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Right, finally is a much better approach than `.then` here, (you can use `.tap`, but that's still ignoring exceptional values. The best approach is to use Bluebird disposers. https://github.com/petkaantonov/bluebird/blob/iterators/API.md#promiseusingpromisedisposer-promise-promisedisposer-promise--function-handler---promise – Benjamin Gruenbaum May 16 '14 at 14:23
0

Your code has a particular resource management problem like Bergi put it. You have to keep remembering when to close the collection and when not to.

The optimal solution would be to use Promise.using however, that's only available in the v2 branch of Bluebird so you're going to have to wait a while.

Until then, you can create your own wrapper method that does more basic scoped resource management:

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

Which would let you do:

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

Now, when the gameSucceed is done, the connection will close itself automatically. This would make gameSucceed itself look like:

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

Generally, you might also want to consider a more OOPish style of coding for your code.

Good luck, and happy coding.

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Thanks man, and I have a question about that first code block. I don't understand how `.timeout(timeout)` works. I think I would understand that if timeout was a function instead of `(timeout === undefined) ? 8000 : timeout` - So I can learn about that, can you link to documentation on whatever `(x == y) ? t : a` is? I've never used that technique. –  May 16 '14 at 21:02
  • @jt0dd sure, this is a Bluebird specific feature that rejects a promise automatically if more than `timeout` milliseconds have passed - the idea is to 'cover your back' so that if you forget to reject or accept the contained promise - you get a loud angry `TimeoutError` to notify you of that, it's a personal touch - and removing it would work just as well only without that functionality. – Benjamin Gruenbaum May 16 '14 at 21:03
  • You talk about the v2 branch of a promise library as if it's common knowledge. I could see it being common for a skilled web dev to know a little something about what's coming up in the next JQuery release, but Bluebird v2.x? How do you know that off the top of your head? –  May 16 '14 at 21:41
  • @jt0dd I'm very involved with the development process of Bluebird, I have some pull requests in and I frequently talk to the main library author (@Esailija in Stack Overflow) and other contributers about it. I was (and still am) one of the main proponents for `Promise.using` in Bluebird in the first place, so whenever I see questions that are interested in that kind of resource management (which is very fundamental in some languages, but insanely hard to get right yourself sometimes eg [this](http://stackoverflow.com/questions/21118201/#comment31797113_21123756)) I get kind of erm.. excited :) – Benjamin Gruenbaum May 16 '14 at 21:50
  • 1
    Well I really appreciate your on-the-ball input. Keep it up. Cool stuff, working on a project like that. Very cool –  May 16 '14 at 21:58
  • I tried with both your and Bergi's way, but I must be misusing yours, because I'm getting an error `createConnection()` has no method `then` jsfiddle.net/MVFv4/1 –  May 21 '14 at 23:58
  • Hi sir, could you help me on this topic? http://stackoverflow.com/q/38802095/6599627 thanks – mahdi pishguy Aug 06 '16 at 08:21