Nikhil Khullar
Nikhil Khullar

Reputation: 733

NPM module "deasync" not working with Hapi JS for MySQL fetch

Current State of the Problem (After drilling down through many issues)

So, it comes down to this. The following independent code [not a prt of the project] fails to function in Hapi JS while works using simple Node.

var Deasync = require('deasync');
var Hapi = require('hapi');
var mysql = require('mysql');

var server = new Hapi.Server();
server.connection({ port: 4000 });

var someAsyncFunction = function () {

  var connection, done, preCoins;
  console.log("fetchPreCoins called!");
  connection = mysql.createConnection({
    host: 'xx.xx.xx.xx',
    port: 3306,
    user: 'xxx',
    password: 'xxxx',
    database: 'db_xxx'
  });
  connection.connect();
  preCoins = null;
  done = false;
  console.log("Just before connection!");
  connection.query('SELECT preCoins from tbl_nzk_user_orbs WHERE userId = "' + "abc" + '"', function(err, rows, fields) {
    if (err) {
      console.log("MySQL DB Failed!");
    }
    if (!rows[0]) {
      connection.end();
      preCoins = null;
      done = true;
      console.log("Did not find");
    } else {
      connection.end();
      preCoins = rows[0].preOrbs;
      done = true;
    }
  });
  require('Deasync').loopWhile(function() {
    return !done;
  });
  console.log("fetchPreCoins about to return");
  return preOrbs;
};

server.route({
    method: 'GET',
    path: '/',
    handler: function (request, reply) {

        reply(someAsyncFunction());
    }
});

server.start(function () {
    console.log('Started server!');
});

Original Question: I have a queer scenario where the output code of my Coffee Script works if I put it in an independent file and run it with Node. But fails to run when it is inside a route in a restful API server written using HAPI JS.

First of all, to give an idea of the use-case; there is already a huge codebase in which this is a late addition for an extra check. I am basically out of time to refactor everything and use this in the default asynchronous way.

After some research, I am using the wonderful npm module "deasync". The following is output code on Coffee compilation, is there a configuration issue or something which prevents HAPI from making request to this external server. There is no error or anything on the terminal console, it just freezes and then times out:

mysql = require('mysql');
deasync = require('deasync');

fetchPreCoins = function(userId) {
  var connection, done, preCoins;
  console.log("fetchPreCoins called!");
  connection = mysql.createConnection({
    host: 'xx.xx.xx.xx',
    port: 3306,
    user: 'xxxx',
    password: 'xxxxx',
    database: 'xxxx'
  });
  connection.connect();
  preCoins = null;
  done = false;
  console.log("Just before connection!");
  connection.query('SELECT preCoins from tbl_xxx WHERE userId = "' + userId + '"', function(err, rows, fields) {
    if (err) {
      console.log("MySQL DB Failed!");
    }
    if (!rows[0]) {
      connection.end();
      preCoins = null;
      done = true;
      console.log("Did not find");
    } else {
      connection.end();
      preCoins = rows[0].preCoins;
      done = true;
    }
  });
  require('deasync').loopWhile(function() {
    return !done;
  });
  console.log("fetchPreCoins about to return");
  return preCoins;
};

Calling it like this works perfectly in an external JS file when executed with Node:

fetchPreCoins();

The packages are properly included in the shrinkwrap and installed for my HAPI-based API server. However, the last log is "Just before connection!" which is where it gets stuck and times out. I really need this method to be synchronous or basically to wait for the response from the MySQL server and return the value.

In case of HAPI route, am just calling this like: preCoins = Helpers.fetchPreCoins(userId)

And I tried the same thing in the external file as:

value = fetchPreCoins(userId);
console.log("Value is: " + value); 

The function returns properly. How can I make this work with Hapi JS. Is there a compatibility issue between Node's MySQL module and HAPI ? I had a look at hapi-mysql but I don't think that solves anything either.

Here is a subset of the HAPI route's code [CoffeeScript] which works perfectly except for when this method is called where it gets stuck.

method: 'POST'
path: '/sessions/users'
config:
    description: 'Create session/get token (login) for a user'
    auth: false
    validate:
        payload:
            password: Joi.string().required()
            username: Joi.string().regex(Models.usernameRegex).lowercase().trim()
    pre:
        [
                assign: 'user'
                method: (request, next) ->
                    Models.User.findOne(username: request.payload.username, '+passwordHash +passwordHashSalt').execute(next)
            ,
                (request, next) ->
                    Helpers.passwordCheck(request.payload.password, request.pre.user.passwordHashSalt, request.pre.user.passwordHash, next)
            # TODO: Could reset shared secret to improve security
        ]
    handler: (request, reply) ->
        prevDate = new Date(request.pre.user.lastSeen).setHours(0,1,0)

        if (Helpers.timeDifference(prevDate, 60) >= 24) 
            if request.pre.user.experience < 100
                addAmount = 4
            else if request.pre.user.experience >= 100 and request.pre.user.experience < 1000
                addAmount = 7
            else if request.pre.user.experience >= 1000 and request.pre.user.experience < 4000
                addAmount = 10
            else if request.pre.user.experience >= 4000 and request.pre.user.experience < 10000
                addAmount = 13
            else if request.pre.user.experience >= 10000
                addAmount = 16                      
            else
                addAmount = 3 # this should not happen though!

            if request.pre.user.membership != "free"
                addAmount += 10 # extra bonus for members

            console.log "user Id: ", request.pre.user.id    
            preCoins = Helpers.fetchPreCoins(request.pre.user.id)
            console.log "Pre coins fetched: ", preCoins
            if preCoins != null 
                if preCoins == request.pre.user.coins
                    request.pre.user.coins += addAmount
                else
                    request.pre.user.coins = preCoins + addAmount # the case when bug had occured and this reverts it   
            else        
                request.pre.user.coins += addAmount

            Helpers.writePreCoins(request.pre.user.id, request.pre.user.coins)  

            Helpers.updateNotification("continuous-day-member-coins", request.pre.user.notifications, request.pre.user)
            Helpers.updateNotification("continuous-day-coins", request.pre.user.notifications, request.pre.user)

        request.pre.user.lastSeen = Date.now()
        request.pre.user.store()
        reply(request.pre.user.toObject(transform: true)) 

Update: Just to test, I stopped the "deasync" for a moment and on the HAPI API console, I get this timeout error. I can't figure out why this should happen since it is a very simple select query:

11:02:16 web.1  | Debug: hapi, internal, implementation, error 
11:02:16 web.1  |     Error: connect ETIMEDOUT
11:02:16 web.1  |   at Connection._handleConnectTimeout (/Users/nikhilkhullar/Desktop/hapi-server/node_modules/mysql/lib/Connection.js:358:13)
11:02:16 web.1  |   at Socket.g (events.js:180:16)
11:02:16 web.1  |   at Socket.emit (events.js:92:17)
11:02:16 web.1  |   at Socket._onTimeout (net.js:326:8)
11:02:16 web.1  |   at Timer.unrefTimeout [as ontimeout] (timers.js:427:13)

Update 2: I have found out that the actual problem is not MySQL as even writing is working when asynchronous. So, it drills down to the fact that the module deasync CAN NOT BE used in Hapi JS. But, sadly this renders this fetch code useless. Is there a way to make this function wait for the reply now, i.e. basically to make connection.query behave synchronously.

Although, it's sad that the same "deasync" is working like a charm outside of Hapi JS...

Upvotes: 0

Views: 707

Answers (1)

vkurchatkin
vkurchatkin

Reputation: 13570

As the original author of deasync I strongly recommend you (and everyone else) not to use it for anything. It's fragile and only works in some subset of cases.

It's definitely not supposed to work inside of running event loop at all: https://github.com/joyent/node/issues/7555#issuecomment-42295204

Upvotes: 1

Related Questions