40

I have a python background and is currently migrating to node.js. I have problem adjusting to node.js due to its asynchronous nature.

For example, I am trying to return a value from a MySQL function.

function getLastRecord(name)
{
    var connection = getMySQL_connection();

    var query_str =
    "SELECT name, " +
    "FROM records " +   
    "WHERE (name = ?) " +
    "LIMIT 1 ";

    var query_var = [name];

    var query = connection.query(query_str, query_var, function (err, rows, fields) {
        //if (err) throw err;
        if (err) {
            //throw err;
            console.log(err);
            logger.info(err);
        }
        else {
            //console.log(rows);
            return rows;
        }
    }); //var query = connection.query(query_str, function (err, rows, fields) {
}

var rows = getLastRecord('name_record');

console.log(rows);

After some reading up, I realize the above code cannot work and I need to return a promise due to node.js's asynchronous nature. I cannot write node.js code like python. How do I convert getLastRecord() to return a promise and how do I handle the returned value?

In fact, what I want to do is something like this;

if (getLastRecord() > 20)
{
    console.log("action");
}

How can this be done in node.js in a readable way?

I would like to see how promises can be implemented in this case using bluebird.

Lorenz Meyer
  • 19,166
  • 22
  • 75
  • 121
guagay_wk
  • 26,337
  • 54
  • 186
  • 295

9 Answers9

80

This is gonna be a little scattered, forgive me.

First, assuming this code uses the mysql driver API correctly, here's one way you could wrap it to work with a native promise:

function getLastRecord(name)
{
    return new Promise(function(resolve, reject) {
        // The Promise constructor should catch any errors thrown on
        // this tick. Alternately, try/catch and reject(err) on catch.
        var connection = getMySQL_connection();

        var query_str =
        "SELECT name, " +
        "FROM records " +   
        "WHERE (name = ?) " +
        "LIMIT 1 ";

        var query_var = [name];

        connection.query(query_str, query_var, function (err, rows, fields) {
            // Call reject on error states,
            // call resolve with results
            if (err) {
                return reject(err);
            }
            resolve(rows);
        });
    });
}

getLastRecord('name_record').then(function(rows) {
    // now you have your rows, you can see if there are <20 of them
}).catch((err) => setImmediate(() => { throw err; })); // Throw async to escape the promise chain

So one thing: You still have callbacks. Callbacks are just functions that you hand to something to call at some point in the future with arguments of its choosing. So the function arguments in xs.map(fn), the (err, result) functions seen in node and the promise result and error handlers are all callbacks. This is somewhat confused by people referring to a specific kind of callback as "callbacks," the ones of (err, result) used in node core in what's called "continuation-passing style", sometimes called "nodebacks" by people that don't really like them.

For now, at least (async/await is coming eventually), you're pretty much stuck with callbacks, regardless of whether you adopt promises or not.

Also, I'll note that promises aren't immediately, obviously helpful here, as you still have a callback. Promises only really shine when you combine them with Promise.all and promise accumulators a la Array.prototype.reduce. But they do shine sometimes, and they are worth learning.

Josh Holbrook
  • 1,611
  • 13
  • 10
  • Oh, and if you do use promises, consider bluebird! It has a number of nice helpers, good well-understood performance, etc. – Josh Holbrook Apr 12 '16 at 01:52
  • If I use bluebird, can I take my `getLastRecord()` function and do something like `Promisify(getLastRecord)` and `getLastRecord()` supports promise? – guagay_wk Apr 12 '16 at 01:59
  • I think http://bluebirdjs.com/docs/api/promise.fromcallback.html is what you want – Josh Holbrook Apr 12 '16 at 02:07
  • 1
    Promise are native to nodejs, no need to use bluebird? – CFrei Apr 27 '16 at 08:16
  • 2
    btw: that `try..catch` block can be avoided, as inside a `new Promise` function a `throw` will automatically calls `reject(err)` if not `catch`ed. (At least for the synchronous part of the function.) – CFrei Apr 27 '16 at 08:17
  • Bluebird isn't *required* obviously, but it comes with a bunch of methods that native promises don't. I've also heard claims that it's faster than the current native implementation, but I don't have the numbers to back that up. – Josh Holbrook Jul 08 '16 at 18:05
  • 1
    This was really helpful. Working through this solution actually helped me with other aspects of my code and pushed me to another level in understanding how to implement Promises and comprehending them on another level of abstraction. – Ken Ingram May 23 '20 at 07:06
  • Can we do this getMySQL_connection().promise() and then await connection.query(...)? – artsnr Nov 13 '20 at 06:54
  • It would be much better to call the method, using: const rows = await getLastRecord(name) Because ‘async/await’ is syntactic sugar for promises, it is perfectly acceptable to call a method that returns a promise with ‘await’. It means you can get the best of both worlds. Asynchronous code with synchronous execution. – Charles Robertson Mar 20 '23 at 14:23
13

I have modified your code to use Q(NPM module) promises. I Assumed your 'getLastRecord()' function that you specified in above snippet works correctly.

You can refer following link to get hold of Q module

Click here : Q documentation

var q = require('q');

function getLastRecord(name)
{

var deferred = q.defer(); // Use Q 
var connection = getMySQL_connection();

var query_str =
"SELECT name, " +
"FROM records " +   
"WHERE (name = ?) " +
"LIMIT 1 ";

var query_var = [name];

var query = connection.query(query_str, query_var, function (err, rows, fields) {
    //if (err) throw err;
    if (err) {
        //throw err;           
        deferred.reject(err);
    }
    else {
        //console.log(rows);           
        deferred.resolve(rows);
    }
}); //var query = connection.query(query_str, function (err, rows, fields) {

return deferred.promise;
}



// Call the method like this
getLastRecord('name_record')
 .then(function(rows){
   // This function get called, when success
   console.log(rows);
  },function(error){
   // This function get called, when error
   console.log(error);

 });
Piyush Sagar
  • 2,931
  • 23
  • 26
  • Please, help: I am stuck with "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" and I have spent a lot of time trying to resolve this issue but unsuccessful. I tried @Piyush Sagar's approach but still getting the error. My sever code is using next.js wrapper arroung express, which uses of their called RequestHandler. This request handler seems to be return reponse before the promise resolves. Withing the callback I use if/else to set response data. That's where i get error, even using the promise approach suggested. – user3422517 Feb 22 '20 at 16:49
12

I am new to Node.js and promises. I was searching for a while for something that will meet my needs and this is what I ended up using after combining several examples I found. I wanted the ability to acquire connection per query and release it right after the query finishes (querySql), or to get a connection from pool and use it within Promise.using scope, or release it whenever I would like it (getSqlConnection). Using this method you can concat several queries one after another without nesting them.

db.js

var mysql = require('mysql');
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({
    host: 'my_aws_host',
    port: '3306',
    user: 'my_user',
    password: 'my_password',
    database: 'db_name'
});

function getSqlConnection() {
    return pool.getConnectionAsync().disposer(function (connection) {
        console.log("Releasing connection back to pool")
        connection.release();
    });
}

function querySql (query, params) {
    return Promise.using(getSqlConnection(), function (connection) {
        console.log("Got connection from pool");
        if (typeof params !== 'undefined'){
            return connection.queryAsync(query, params);
        } else {
            return connection.queryAsync(query);
        }
    });
};

module.exports = {
    getSqlConnection : getSqlConnection,
    querySql : querySql
};

usage_route.js

var express = require('express');
var router = express.Router();

var dateFormat = require('dateformat');
var db = require('../my_modules/db');
var getSqlConnection = db.getSqlConnection;
var querySql = db.querySql;

var Promise = require("bluebird");

function retrieveUser(token) {
  var userQuery = "select id, email from users where token = ?";
  return querySql(userQuery, [token])
     .then(function(rows){
        if (rows.length == 0) {
          return Promise.reject("did not find user");
        }

        var user = rows[0];
        return user;
     });
}

router.post('/', function (req, res, next) {

  Promise.resolve().then(function () {
    return retrieveUser(req.body.token);
  })
    .then(function (user){
      email = user.email;
      res.status(200).json({ "code": 0, "message": "success", "email": email});
    })
    .catch(function (err) {
      console.error("got error: " + err);
      if (err instanceof Error) {
        res.status(400).send("General error");
      } else {
        res.status(200).json({ "code": 1000, "message": err });
      }
    });
});

module.exports = router;
Community
  • 1
  • 1
MikeL
  • 5,385
  • 42
  • 41
5

I am still a bit new to node, so maybe I missed something let me know how it works out. Instead of triggering async node just forces it on you, so you have to think ahead and plan it.

const mysql = require('mysql');
const db = mysql.createConnection({
          host: 'localhost', 
          user: 'user', password: 'password', 
          database: 'database',
      });
      db.connect((err) => {
          // you should probably add reject instead of throwing error
          // reject(new Error()); 
          if(err){throw err;}
          console.log('Mysql: Connected');
      });
      db.promise = (sql) => {
          return new Promise((resolve, reject) => {
              db.query(sql, (err, result) => {
                if(err){reject(new Error());}
                else{resolve(result);}
              });
          });
      };

Here I am using the mysql module like normal, but instead I created a new function to handle the promise ahead of time, by adding it to the db const. (you see this as "connection" in a lot of node examples.

Now lets call a mysql query using the promise.

      db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
      .then((result)=>{
          console.log(result);
      }).catch((err)=>{
          console.log(err);
      });

What I have found this useful for is when you need to do a second query based on the first query.

      db.promise("SELECT * FROM users WHERE username='john doe' LIMIT 1;")
      .then((result)=>{
          console.log(result);
          var sql = "SELECT * FROM friends WHERE username='";
              sql = result[0];
              sql = "';"
          return db.promise(sql);
      }).then((result)=>{
          console.log(result);
      }).catch((err)=>{
          console.log(err);
      });

You should actually use the mysql variables, but this should at least give you an example of using promises with mysql module.

Also with above you can still continue to use the db.query the normal way anytime within these promises, they just work like normal.

Hope this helps with the triangle of death.

Dillon Burnett
  • 473
  • 4
  • 11
4

You don't need to use promises, you can use a callback function, something like that:

function getLastRecord(name, next)
{
    var connection = getMySQL_connection();

    var query_str =
    "SELECT name, " +
    "FROM records " +    
    "LIMIT 1 ";

    var query_var = [name];

    var query = connection.query(query_str, query_var, function (err, rows, fields) {
        //if (err) throw err;
        if (err) {
            //throw err;
            console.log(err);
            logger.info(err);
            next(err);
        }
        else {
            //console.log(rows);
            next(null, rows);
        }
    }); //var query = connection.query(query_str, function (err, rows, fields) {
}

getLastRecord('name_record', function(err, data) {
   if(err) {
      // handle the error
   } else {
      // handle your data

   }
});
Jordi Ruiz
  • 487
  • 2
  • 11
  • Thanks. Is there any way to do something like this `if (getLastRecord() > 20>` or at least make it readable? – guagay_wk Apr 11 '16 at 12:59
  • 1
    @user16891328 You have to do it inside of the callback, `getLastRecord('name_record', function(err, data) { if(err) {} else { if(data.length > 20) }});` – Jordi Ruiz Apr 11 '16 at 13:04
  • Ok. Thanks. Seems like there's no other option. The code is less readable than python. – guagay_wk Apr 11 '16 at 13:05
  • Well, the problem is its asynchronous nature, you have to wait for the callback. – Jordi Ruiz Apr 11 '16 at 13:07
  • Why do you think using callback in this case is better than promise? Would using promise make the code more readable? – guagay_wk Apr 11 '16 at 13:35
  • 1
    @user16891328 you can use promises if you think your code will be more readable. `getLastRecord('name_record').then(function(data) { if(data.length > 20) { // dosomething } });` – Jordi Ruiz Apr 11 '16 at 13:38
3

To answer your initial question: How can this be done in node.js in a readable way?

There is a library called co, which gives you the possibility to write async code in a synchronous workflow. Just have a look and npm install co.

The problem you face very often with that approach, is, that you do not get Promise back from all the libraries you like to use. So you have either wrap it yourself (see answer from @Joshua Holbrook) or look for a wrapper (for example: npm install mysql-promise)

(Btw: its on the roadmap for ES7 to have native support for this type of workflow with the keywords async await, but its not yet in node: node feature list.)

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
CFrei
  • 3,552
  • 1
  • 15
  • 29
3

Using the package promise-mysql the logic would be to chain promises using then(function(response){your code})

and

catch(function(response){your code}) to catch errors from the "then" blocks preceeding the catch block.

Following this logic, you will pass query results in objects or arrays using return at the end of the block. The return will help passing the query results to the next block. Then, the result will be found in the function argument (here it is test1). Using this logic you can chain several MySql queries and the code that is required to manipulate the result and do whatever you want.

the Connection object is created to be global because every object and variable created in every block are only local. Don't forget that you can chain more "then" blocks.

var config = {
    host     : 'host',
    user     : 'user',
    password : 'pass',
    database : 'database',

  };
  var mysql = require('promise-mysql');
  var connection;
  let thename =""; // which can also be an argument if you embed this code in a function

  mysql.createConnection(config
  ).then(function(conn){
      connection = conn;
      let test = connection.query('select name from records WHERE name=? LIMIT 1',[thename]);
      return test;
  }).then(function(test1){
      console.log("test1"+JSON.stringify(test1)); // result of previous block
      var result = connection.query('select * from users'); // A second query if you want
      connection.end();
 connection = {};
      return result;
  }).catch(function(error){
      if (connection && connection.end) connection.end();
      //logs out the error from the previous block (if there is any issue add a second catch behind this one)
      console.log(error);
  });
Nicolas Guérinet
  • 2,086
  • 1
  • 29
  • 38
2

This can be achieved quite simply, for example with bluebird, as you asked:

var Promise = require('bluebird');

function getLastRecord(name)
{
    return new Promise(function(resolve, reject){
        var connection = getMySQL_connection();

        var query_str =
            "SELECT name, " +
            "FROM records " +
            "WHERE (name = ?) " +
            "LIMIT 1 ";

        var query_var = [name];

        var query = connection.query(query_str, query_var, function (err, rows, fields) {
            //if (err) throw err;
            if (err) {
                //throw err;
                console.log(err);
                logger.info(err);
                reject(err);
            }
            else {
                resolve(rows);
                //console.log(rows);
            }
        }); //var query = connection.query(query_str, function (err, rows, fields) {
    });
}


getLastRecord('name_record')
    .then(function(rows){
        if (rows > 20) {
            console.log("action");
        }
    })
    .error(function(e){console.log("Error handler " + e)})
    .catch(function(e){console.log("Catch handler " + e)});
Andrej Burcev
  • 355
  • 2
  • 7
2

May be helpful for others, extending @Dillon Burnett answer

Using async/await and params

db.promise = (sql, params) => {
    return new Promise((resolve, reject) => {
      db.query(sql,params, (err, result) => {
          if(err){reject(new Error());}
             else{resolve(result);}
          });
       });
};
module.exports = db;

   

 async connection(){
    const result = await db.promise("SELECT * FROM users WHERE username=?",[username]);
       return result;
    }
uday214125
  • 569
  • 1
  • 8
  • 22
  • This is the best answer. Async/await is so much easier to read. Any method that returns a promise can be called with await. It creates synchronous execution, out of asynchronous code. – Charles Robertson Mar 20 '23 at 14:28