95

I have a simple node module which connects to a database and has several functions to receive data, for example this function:


dbConnection.js:

import mysql from 'mysql';

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

export default {
  getUsers(callback) {
    connection.connect(() => {
      connection.query('SELECT * FROM Users', (err, result) => {
        if (!err){
          callback(result);
        }
      });
    });
  }
};

The module would be called this way from a different node module:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

I would like to use promises instead of callbacks in order to return the data. So far I've read about nested promises in the following thread: Writing Clean Code With Nested Promises, but I couldn't find any solution that is simple enough for this use case. What would be the correct way to return result using a promise?

Lior Erez
  • 1,852
  • 2
  • 19
  • 24
  • 1
    See [Adapting Node](https://github.com/kriskowal/q#adapting-node), if you’re using kriskowal’s Q library. – Bertrand Marron Feb 10 '15 at 13:20
  • @leo.249: Have you read the Q documentation? Have you tried to apply it to your code already - if yes, please post your attempt (even if not working)? Where exactly are you stuck? You seem to have found a non-simple solution, please post it. – Bergi Feb 10 '15 at 13:42
  • 3
    @leo.249 Q is practically unmaintained - last commit was 3 months ago. Only the v2 branch is interesting to Q developers and that's not even close to being production ready anyway. There are unaddressed issues without comments in the issue tracker from October. I strongly suggest you consider a well maintained promise library. – Benjamin Gruenbaum Feb 10 '15 at 14:27
  • 2
    Super related [**How to convert a callback API to promises**](http://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises) – Benjamin Gruenbaum Feb 10 '15 at 19:14

8 Answers8

105

Using the Promise class

I recommend to take a look at MDN's Promise docs which offer a good starting point for using Promises. Alternatively, I am sure there are many tutorials available online.:)

Note: Modern browsers already support ECMAScript 6 specification of Promises (see the MDN docs linked above) and I assume that you want to use the native implementation, without 3rd party libraries.

As for an actual example...

The basic principle works like this:

  1. Your API is called
  2. You create a new Promise object, this object takes a single function as constructor parameter
  3. Your provided function is called by the underlying implementation and the function is given two functions - resolve and reject
  4. Once you do your logic, you call one of these to either fullfill the Promise or reject it with an error

This might seem like a lot so here is an actual example.

exports.getUsers = function getUsers () {
  // Return the Promise right away, unless you really need to
  // do something before you create a new Promise, but usually
  // this can go into the function below
  return new Promise((resolve, reject) => {
    // reject and resolve are functions provided by the Promise
    // implementation. Call only one of them.

    // Do your logic here - you can do WTF you want.:)
    connection.query('SELECT * FROM Users', (err, result) => {
      // PS. Fail fast! Handle errors first, then move to the
      // important stuff (that's a good practice at least)
      if (err) {
        // Reject the Promise with an error
        return reject(err)
      }

      // Resolve (or fulfill) the promise with data
      return resolve(result)
    })
  })
}

// Usage:
exports.getUsers()  // Returns a Promise!
  .then(users => {
    // Do stuff with users
  })
  .catch(err => {
    // handle errors
  })

Using the async/await language feature (Node.js >=7.6)

In Node.js 7.6, the v8 JavaScript compiler was upgraded with async/await support. You can now declare functions as being async, which means they automatically return a Promise which is resolved when the async function completes execution. Inside this function, you can use the await keyword to wait until another Promise resolves.

Here is an example:

exports.getUsers = async function getUsers() {
  // We are in an async function - this will return Promise
  // no matter what.

  // We can interact with other functions which return a
  // Promise very easily:
  const result = await connection.query('select * from users')

  // Interacting with callback-based APIs is a bit more
  // complicated but still very easy:
  const result2 = await new Promise((resolve, reject) => {
    connection.query('select * from users', (err, res) => {
      return void err ? reject(err) : resolve(res)
    })
  })
  // Returning a value will cause the promise to be resolved
  // with that value
  return result
}
Robert Rossmann
  • 11,931
  • 4
  • 42
  • 73
  • 14
    Promises are part of ECMAScript 2015 specification and v8 used by Node v0.12 provides implementation of this part of the spec. So yes, they are not part of Node core - they are part of the language. – Robert Rossmann Jul 28 '15 at 17:54
  • 1
    Good to know, I was under the impression that to use Promises you would need to install a npm package and use require(). I found the promise package on npm which implements bare bones/A++ style and have used that, but am still new to node itself (not JavaScript). – macguru2000 Jul 28 '15 at 17:59
  • This is my favorite way to write promises and architect async code, mainly because it is a consistent pattern, easily read, and allows for highly structured code. –  Jun 01 '16 at 11:23
31

With bluebird you can use Promise.promisifyAll (and Promise.promisify) to add Promise ready methods to any object.

var Promise = require('bluebird');
// Somewhere around here, the following line is called
Promise.promisifyAll(connection);

exports.getUsersAsync = function () {
    return connection.connectAsync()
        .then(function () {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

And use like this:

getUsersAsync().then(console.log);

or

// Spread because MySQL queries actually return two resulting arguments, 
// which Bluebird resolves as an array.
getUsersAsync().spread(function(rows, fields) {
    // Do whatever you want with either rows or fields.
});

Adding disposers

Bluebird supports a lot of features, one of them is disposers, it allows you to safely dispose of a connection after it ended with the help of Promise.using and Promise.prototype.disposer. Here's an example from my app:

function getConnection(host, user, password, port) {
    // connection was already promisified at this point

    // The object literal syntax is ES6, it's the equivalent of
    // {host: host, user: user, ... }
    var connection = mysql.createConnection({host, user, password, port});
    return connection.connectAsync()
        // connect callback doesn't have arguments. return connection.
        .return(connection) 
        .disposer(function(connection, promise) { 
            //Disposer is used when Promise.using is finished.
            connection.end();
        });
}

Then use it like this:

exports.getUsersAsync = function () {
    return Promise.using(getConnection()).then(function (connection) {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

This will automatically end the connection once the promise resolves with the value (or rejects with an Error).

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
  • 3
    Excellent answer, I ended up using bluebird instead of Q thanks to you, thank you! – Lior Erez Feb 10 '15 at 16:12
  • 2
    Keep in mind that using promises you agree to use `try-catch` on each call. So if you do it quite often, and your code complexity is similar to the sample, then you should reconsider this. – Andrey Popov Sep 23 '15 at 15:32
14

Node.js version 8.0.0+:

You don't have to use bluebird to promisify the node API methods anymore. Because, from version 8+ you can use native util.promisify:

const util = require('util');

const connectAsync = util.promisify(connection.connectAsync);
const queryAsync = util.promisify(connection.queryAsync);

exports.getUsersAsync = function () {
    return connectAsync()
        .then(function () {
            return queryAsync('SELECT * FROM Users')
        });
};

Now, don't have to use any 3rd party lib to do the promisify.

asmmahmud
  • 4,844
  • 2
  • 40
  • 47
3

Assuming your database adapter API doesn't output Promises itself you can do something like:

exports.getUsers = function () {
    var promise;
    promise = new Promise();
    connection.connect(function () {
        connection.query('SELECT * FROM Users', function (err, result) {
            if(!err){
                promise.resolve(result);
            } else {
                promise.reject(err);
            }
        });
    });
    return promise.promise();
};

If the database API does support Promises you could do something like: (here you see the power of Promises, your callback fluff pretty much disappears)

exports.getUsers = function () {
    return connection.connect().then(function () {
        return connection.query('SELECT * FROM Users');
    });
};

Using .then() to return a new (nested) promise.

Call with:

module.getUsers().done(function (result) { /* your code here */ });

I used a mockup API for my Promises, your API might be different. If you show me your API I can tailor it.

Madara's Ghost
  • 172,118
  • 50
  • 264
  • 308
Halcyon
  • 57,230
  • 10
  • 89
  • 128
  • 2
    What promise library does have a `Promise` constructor and a `.promise()` method? – Bergi Feb 10 '15 at 13:26
  • Thank you. I'm just practicing some node.js and what I posted was all there is to it, very simple example to figure out how to use promises. Your solution looks good but what npm package would I have to install in order to use `promise = new Promise();`? – Lior Erez Feb 10 '15 at 13:29
  • While your API now returns a Promise, you haven't gotten rid of the pyramid of doom, or made an example of how promises work to replace callbacks. – Madara's Ghost Feb 10 '15 at 13:33
  • @leo.249 I don't know, any Promise library that is compliant with Promises/A+ should be good. See: https://promisesaplus.com/@Bergi it's irrelevant. @SecondRikudo if the API you're interfacing with doesn't support `Promises` then you're stuck with using callbacks. Once your get into promise territory the 'pyramid' disappears. See the second code example on how that would work. – Halcyon Feb 10 '15 at 13:34
  • @Halcyon See my answer. Even an existing API that uses callbacks can be "promisified" into a Promise ready API, which results in much cleaner code altogether. – Madara's Ghost Feb 10 '15 at 13:35
  • Sure, but then you might as well write your own database API wrapper. This half-half solution seems messy. I wouldn't dare run something like that in production. – Halcyon Feb 10 '15 at 13:39
  • @Halcyon: No, Promises/A+ only specifies `then` but no `Promise()` constructor – Bergi Feb 10 '15 at 13:40
  • @Halcyon promisifcation is not magic. It goes over the properties of an object (and its prototype) and does what most answers here (including yours) do, while not touching the object's original properties (notice `connectAsync`, `queryAsync`, etc). There's no "half-half" situation here. – Madara's Ghost Feb 10 '15 at 13:41
  • Sure, but I don't see how it can magically implement error propagation and promise cancellation. These are the features that make Promises so powerful. That's what I mean by half-half; you only get the 'success' line. – Halcyon Feb 10 '15 at 13:46
  • No, you get `.catch()` and `.cancel()` and everything else Bluebird supports with any proper trusted Promise. – Madara's Ghost Feb 10 '15 at 13:49
  • `connection.query` accepts, as a second parameter `function (err, result)` how does _promisify_ turn that into a proper done/reject? I don't see how it could. Sure you get the `catch` but you don't get the `throw`. – Halcyon Feb 10 '15 at 13:56
  • @Halcyon how can I make my database API support promises like you described in the second solution? – Lior Erez Feb 10 '15 at 14:10
3

2019:

Use that native module const {promisify} = require('util'); to conver plain old callback pattern to promise pattern so you can get benfit from async/await code

const {promisify} = require('util');
const glob = promisify(require('glob'));

app.get('/', async function (req, res) {
    const files = await glob('src/**/*-spec.js');
    res.render('mocha-template-test', {files});
});

pery mimon
  • 7,713
  • 6
  • 52
  • 57
2

When setting up a promise you take two parameters, resolve and reject. In the case of success, call resolve with the result, in the case of failure call reject with the error.

Then you can write:

getUsers().then(callback)

callback will be called with the result of the promise returned from getUsers, i.e. result

Tom
  • 7,994
  • 8
  • 45
  • 62
2

Using the Q library for example:

function getUsers(param){
    var d = Q.defer();

    connection.connect(function () {
    connection.query('SELECT * FROM Users', function (err, result) {
        if(!err){
            d.resolve(result);
        }
    });
    });
    return d.promise;   
}
LeftyX
  • 35,328
  • 21
  • 132
  • 193
satchcoder
  • 797
  • 5
  • 11
0

Below code works only for node -v > 8.x

I use this Promisified MySQL middleware for Node.js

read this article Create a MySQL Database Middleware with Node.js 8 and Async/Await

database.js

var mysql = require('mysql'); 

// node -v must > 8.x 
var util = require('util');


//  !!!!! for node version < 8.x only  !!!!!
// npm install util.promisify
//require('util.promisify').shim();
// -v < 8.x  has problem with async await so upgrade -v to v9.6.1 for this to work. 



// connection pool https://github.com/mysqljs/mysql   [1]
var pool = mysql.createPool({
  connectionLimit : process.env.mysql_connection_pool_Limit, // default:10
  host     : process.env.mysql_host,
  user     : process.env.mysql_user,
  password : process.env.mysql_password,
  database : process.env.mysql_database
})


// Ping database to check for common exception errors.
pool.getConnection((err, connection) => {
if (err) {
    if (err.code === 'PROTOCOL_CONNECTION_LOST') {
        console.error('Database connection was closed.')
    }
    if (err.code === 'ER_CON_COUNT_ERROR') {
        console.error('Database has too many connections.')
    }
    if (err.code === 'ECONNREFUSED') {
        console.error('Database connection was refused.')
    }
}

if (connection) connection.release()

 return
 })

// Promisify for Node.js async/await.
 pool.query = util.promisify(pool.query)



 module.exports = pool

You must upgrade node -v > 8.x

you must use async function to be able to use await.

example:

   var pool = require('./database')

  // node -v must > 8.x, --> async / await  
  router.get('/:template', async function(req, res, next) 
  {
      ...
    try {
         var _sql_rest_url = 'SELECT * FROM arcgis_viewer.rest_url WHERE id='+ _url_id;
         var rows = await pool.query(_sql_rest_url)

         _url  = rows[0].rest_url // first record, property name is 'rest_url'
         if (_center_lat   == null) {_center_lat = rows[0].center_lat  }
         if (_center_long  == null) {_center_long= rows[0].center_long }
         if (_center_zoom  == null) {_center_zoom= rows[0].center_zoom }          
         _place = rows[0].place


       } catch(err) {
                        throw new Error(err)
       }
hoogw
  • 4,982
  • 1
  • 37
  • 33