57

I have a Node.js/Express app that queries a MySQL db within the route and displays the result to the user. My problem is how do I run the queries and block until both queries are done before redirecting the user to the page they requested?

In my example I have 2 queries that need to finish before I render the page. I can get the queries to run synchronously if i nest query 2 inside the 'result' callback of query 1. This however will become very convoluted when the number of queries increase.

How do I go about running multiple (in this case 2) database queries synchronously without nesting the subsequent query in the prior query's 'result' callback?

I've looked at the 'Flow control / Async goodies' in the Node modules and tried flow-js but I can't get it to work with the async queries.

Listed below are the 2 queries that I'm attempting to execute from the '/home' route. Can the Node experts explain the 'right' way to do this.

app.get('/home', function (req,res) {
    var user_array = [];
    var title_array = [];

    // first query
    var sql = 'select user_name from users';
    db.execute(sql)
        .addListener('row', function(r) {
            user_array.push( { user_name: r.user_name } );
        })
        .addListener('result', function(r) {
            req.session.user_array = user_array;
        });

    // second query
    var sql = 'select title from code_samples';
    db.execute(sql)
        .addListener('row', function(r) {
            title_array.push( { title: r.title } );
        })
        .addListener('result', function(r) {
            req.session.title_array = title_array;
        });

        // because the queries are async no data is returned to the user
        res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }});
});
Rick
  • 927
  • 2
  • 8
  • 10
  • I .. uh ... I think you're doing this completely wrong ... The whole point is for asynchronicity. It's actually a much better programming model. Have you any understanding of what node.js is for? It's to allow you to use evented callbacks to reduce the amount of spintime on individual threads, thus utilizing the system more. I really suggest you adopt the "do things in the callback" model, as that is the intent. Or switch to Ruby. – jcolebrand Jul 06 '11 at 14:30
  • 3
    So how would you go about doing multiple async queries and return the data to the EJS view? – Rick Jul 06 '11 at 15:24
  • In callbacks. But that's just me. – jcolebrand Jul 06 '11 at 15:27
  • 4
    So if i have to do 10 queries i would stack/nest each query inside the previous callback? Wont that get convoluted pretty quick? – Rick Jul 06 '11 at 15:29
  • 1
    yes, there's that approach. How about, instead, you figure out how to have the database return 10 result sets as one query instead? – jcolebrand Jul 06 '11 at 15:36
  • For simple queries you could probably get that to work but for cases where you can't join tables because the queries are completely unrelated that would not be feasible. What about using an Async module to like flow-js (https://github.com/willconant/flow-js) to do this? – Rick Jul 06 '11 at 17:00
  • that's totally your perogative. However, what you said is inherently false. I routinely return many resultsets on my queries. I just think about the process differently than you do. – jcolebrand Jul 06 '11 at 17:39
  • Do you have an example of a single query returning multiple unrelated result sets? – Rick Jul 06 '11 at 18:39
  • 1
    Yes, which platform are you going to query against? Shouldn't make a difference, but it could. Here's raw sql `select * from customers; select * from products;` – jcolebrand Jul 06 '11 at 19:51
  • Rick, if you want to fire multiple queries that may contain joins and also want to use asynchronous approach then you can use async module of node js –  Jun 07 '16 at 10:19

6 Answers6

56

The goal with node is not to care what order things happen in. This can complicate some scenarios. There is no shame in nesting callbacks. Once you are used to how it looks, you may find that you actually prefer that style. I do; it is very clear what order callbacks will fire. You can forgo the anonymous functions to make it less verbose if you have to.

If you are willing to restructure your code a bit, you can use the "typical" nested callback method. If you want to avoid callbacks, there are numerous async frameworks that will try and help you do this. One that you might want to check out is async.js (https://github.com/fjakobs/async.js). Example of each:

app.get('/home', function (req,res) {
    var lock = 2;
    var result = {};
    result.user_array = [];
    result.title_array = [];

    var finishRequest = function(result) {
        req.session.title_array = result.title_array;
        req.session.user_array = result.user_array;
        res.render('home.ejs', {layout: false, locals: { user_name: result.user_array, title: result.title_array }});
    };

    // first query
    var q1 = function(fn) {
      var sql = 'select user_name from users';
      db.execute(sql)
          .addListener('row', function(r) {
              result.user_array.push( { user_name: r.user_name } );
          })
          .addListener('result', function(r) {
              return fn && fn(null, result);
        });
    };

    // second query
    var q2 = function(fn) {
      var sql = 'select title from code_samples';
      db.execute(sql)
          .addListener('row', function(r) {
              result.title_array.push( { title: r.title } );
          })
          .addListener('result', function(r) {
              return fn && fn(null, result);
          });
    }

    //Standard nested callbacks
    q1(function (err, result) {
      if (err) { return; //do something}

      q2(function (err, result) {
        if (err) { return; //do something}

        finishRequest(result);
      });
    });

    //Using async.js
    async.list([
        q1,
        q2,
    ]).call().end(function(err, result) {
      finishRequest(result);
    });

});

For a one-off, I would probably just use a reference counting type approach. Simply keep track of how many queries you want to execute and render the response when they have all finished.

app.get('/home', function (req,res) {
    var lock = 2;
    var user_array = [];
    var title_array = [];

    var finishRequest = function() {
        res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }});
    }

    // first query
    var sql = 'select user_name from users';
    db.execute(sql)
        .addListener('row', function(r) {
            user_array.push( { user_name: r.user_name } );
        })
        .addListener('result', function(r) {
            req.session.user_array = user_array;
            lock -= 1;

            if (lock === 0) {
              finishRequest();
            }
        });

    // second query
    var sql = 'select title from code_samples';
    db.execute(sql)
        .addListener('row', function(r) {
            title_array.push( { title: r.title } );
        })
        .addListener('result', function(r) {
            req.session.title_array = title_array;
            lock -= 1;

            if (lock === 0) {
              finishRequest();
            }
        });
});

An even nicer approach would be to simply call finishRequest() in each 'result' callback an check for non-empty arrays before you render the response. Whether that will work in your case depends on your requirements.

jslatts
  • 9,307
  • 5
  • 35
  • 38
  • I always assumed that reference counting like this was a "hack" but it seems valid when callbacks are involved. Thanks for the example. – Rick Jul 07 '11 at 14:00
  • It is a bit hacky and I would certainly invest in a more robust method if I was going to be writing that all over my code. If you are willing to restructure a bit, you can avoid that. I updated my answer with more examples. – jslatts Jul 07 '11 at 14:53
  • Thanks for the second example with async.js It's definitely a cleaner pattern. Thanks! – Rick Jul 07 '11 at 16:08
  • Thanks very much for your helpful explanation! I ended up using caolan's async library, but your explanation got me started in the right direction. Thanks! – andyengle Jan 19 '12 at 23:21
18

Here's a really easy trick to handle multiple callbacks.

var after = function _after(count, f) {
  var c = 0, results = [];
  return function _callback() {
    switch (arguments.length) {
      case 0: results.push(null); break;
      case 1: results.push(arguments[0]); break;
      default: results.push(Array.prototype.slice.call(arguments)); break;
    }
    if (++c === count) {
      f.apply(this, results);
    }
  };
};

Example

Usage:

var handleDatabase = after(2, function (res1, res2) {
  res.render('home.ejs', { locals: { r1: res1, r2: res2 }):
})

db.execute(sql1).on('result', handleDatabase);
db.execute(sql2).on('result', handleDatabase);

So basically you need reference counting. This is the standard approach in these situations. I actually use this small utility function instead of flow control.

If you want a full blown flow control solution I would recommend futuresJS

Raynos
  • 166,823
  • 56
  • 351
  • 396
14

I find that the async library is the best for things like this. https://github.com/caolan/async#parallel

I can't test this or anything, so forgive me if there are some typos. I refactored your query function to be reusable. So, calling queryRows will return a function that matches the format of the async module's parallel callback functions. After both queries are complete, it will call the last function and pass the result of the two queries as an argument, which you can read to pass to your template.

function queryRows(col, table) {
  return function(cb) {
    var rows = [];
    db.execute('SELECT ' + col + ' FROM ' + table)
      .on('row', function(r) {
        rows.push(r)        
      })
      .on('result', function() {
        cb(rows);
      });
  }
}

app.get('/home', function(req, res) {
  async.parallel({
    users: queryRow('user_name', 'users'),
    titles: queryRow('title', 'code_samples')
  },
  function(result) {
    res.render('home.ejs', { 
      layout: false,
      locals: {user_name: result.users, title: result.titles} 
    });
  });
});
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
  • 1
    I like this. The benefit to this answer is it doesn't need any reference counting. I'll definitely give it a try but probably move all the queries to stored procedures so that there is no need for building complex dynamic sql statements. – Rick Jul 11 '11 at 21:08
  • 1
    Letting a helper library implement all of the fancy asynchronous stuff like this is much easier. The library probable does either reference counting or fancy callback management, but you never have to worry about it. – loganfsmyth Jul 12 '11 at 17:50
  • 1
    The answer I came to SO for. This should rank higher IMO, a solution to a common JS problem that many ppl. look for and instead receive walls of text on the philosophy of the callback. – aliopi Sep 01 '15 at 09:54
  • These days I'd probably use Promises anyway now that the've been incorporated into ES6. – loganfsmyth Sep 01 '15 at 15:06
  • I'm using sync-mysql for a hobby project. The advantage is that the programming model is much simpler and cleaner. But it's much slower. That's fine; I'm not worried about performance, so not looking into pooling or anything like that. I also implemented it using the mysql package and Promises. That works and runs much faster, but the code is much heavier. I wound up leaning on Promise.all(). – Conrad Damon Nov 10 '17 at 00:20
6

There are some solutions here, but in my opinion the best solution is to make the code synchronously in a very easy way.

You could use the "synchonize" package.

Just

npm install synchronize

Then var sync = require(synchronize);

Put logic which should be synchronous into a fiber by using

sync.fiber(function() { //put your logic here }

An example for two mysql queries:

var express = require('express');
var bodyParser = require('body-parser');
var mysql = require('mysql');
var sync = require('synchronize');

var db = mysql.createConnection({
    host     : 'localhost',
    user     : 'user',
    password : 'password',
    database : 'database'
});

db.connect(function(err) {
    if (err) {
        console.error('error connecting: ' + err.stack);
        return;
    }
});

function saveSomething() {
    var post  = {id: newId};
    //no callback here; the result is in "query"
    var query = sync.await(db.query('INSERT INTO mainTable SET ?', post, sync.defer()));
    var newId = query.insertId;
    post  = {foreignKey: newId};
    //this query can be async, because it doesn't matter in this case
    db.query('INSERT INTO subTable SET ?', post, function(err, result) {
        if (err) throw err;
    });
}

When "saveSomething()" is called, it inserts a row in a main table and receives the last inserted id. After that the code below will be executed. No need for nesting promises or stuff like that.

EscapeNetscape
  • 2,892
  • 1
  • 33
  • 32
1

You can use fibers to write pseudo-synchronous code with Node.JS take a look at these tests for DB https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee they are asynchronous but looks like synchronous, more details http://alexeypetrushin.github.com/synchronize

Alex Craft
  • 13,598
  • 11
  • 69
  • 133
1

option one: if all your queries related to each other, create stored procedure, put all your data logic into it and have a single db.execute

option two: if your db uses one connection then commands a guaranteed to be executed serially and you can use this as async helper

db.execute(sql1).on('row', function(r) {
   req.session.user_array.push(r.user);
});
db.execute(sql2)
.on('row', function(r) {
   req.session.title_array.push(r.title);
})
.on('end'), function() {
   // render data from req.session
});
Andrey Sidorov
  • 24,905
  • 4
  • 62
  • 75
  • So does that mean if I use database connection pooling with the async helper the queries could still run in parallel? – Rick Jul 07 '11 at 13:08
  • 1
    yes. If two queries went to two different connections you cant predict which one is finished first. On MySql side each connection is processed in separate thread. Simple example: `pool.query('select sleep (2)'); pool.query('select sleep(1)')` - most likely second query will go to separate link and most likely it is finished one second before first query. If you send queries using one link they are processed serially (and probably in the same thread on mysql side) one after another. – Andrey Sidorov Jul 08 '11 at 00:31