30

In the code

var stuff_i_want = '';
stuff_i_want = get_info(parm);

And the function get_info:

get_info(data){
      var sql = "SELECT a from b where info = data"
      connection.query(sql, function(err, results){
            if (err){ 
              throw err;
            }
            console.log(results[0].objid); // good
            stuff_i_want = results[0].objid;  // Scope is larger than function
            console.log(stuff_i_want); // Yep. Value assigned..
    }

in the larger scope

stuff_i_want = null

What am i missing regarding returning mysql data and assigning it to a variable?

============ New code per Alex suggestion

var parent_id = '';
    get_info(data, cb){
          var sql = "SELECT a from b where info = data"
          connection.query(sql, function(err, results){
                if (err){ 
                  throw err;
                }
                return cb(results[0].objid);  // Scope is larger than function
    }

==== New Code in Use

 get_data(parent_recording, function(result){ 
    parent_id = result;
    console.log("Parent ID: " + parent_id); // Data is delivered
  });

However

console.log("Parent ID: " + parent_id);

In the scope outside the function parent_id is null

Ken Ingram
  • 1,538
  • 5
  • 27
  • 52

6 Answers6

51

You're going to need to get your head around asynchronous calls and callbacks with javascript, this isn't C#, PHP, etc...

Here's an example using your code:

function get_info(data, callback){
      
      var sql = "SELECT a from b where info = data";

      connection.query(sql, function(err, results){
            if (err){ 
              throw err;
            }
            console.log(results[0].objid); // good
            stuff_i_want = results[0].objid;  // Scope is larger than function

            return callback(results[0].objid);
    })
}


//usage

var stuff_i_want = '';

 get_info(parm, function(result){
    stuff_i_want = result;

    //rest of your code goes in here
 });

When you call get_info this, in turn, calls connection.query, which takes a callback (that's what function(err, results) is
The scope is then passed to this callback, and so on.

Welcome to javascript callback hell...

It's easy when you get the hang of it, just takes a bit of getting used to, coming from something like C#

Alex
  • 37,502
  • 51
  • 204
  • 332
36

I guess what you really want to do here is returning a Promise object with the results. This way you can deal with the async operation of retrieving data from the DBMS: when you have the results, you make use of the Promise resolve function to somehow "return the value" / "resolve the promise".

Here's an example:

getEmployeeNames = function(){
  return new Promise(function(resolve, reject){
    connection.query(
        "SELECT Name, Surname FROM Employee", 
        function(err, rows){                                                
            if(rows === undefined){
                reject(new Error("Error rows is undefined"));
            }else{
                resolve(rows);
            }
        }
    )}
)}

On the caller side, you use the then function to manage fulfillment, and the catch function to manage rejection.

Here's an example that makes use of the code above:

getEmployeeNames()
.then(function(results){
  render(results)
})
.catch(function(err){
  console.log("Promise rejection error: "+err);
})

At this point you can set up the view for your results (which are indeed returned as an array of objects):

render = function(results){ for (var i in results) console.log(results[i].Name) }

Edit I'm adding a basic example on how to return HTML content with the results, which is a more typical scenario for Node. Just use the then function of the promise to set the HTTP response, and open your browser at http://localhost:3001

require('http').createServer( function(req, res){
if(req.method == 'GET'){
    if(req.url == '/'){
        res.setHeader('Content-type', 'text/html');
        getEmployeeNames()
        .then(function(results){
          html = "<h2>"+results.length+" employees found</h2>"
          html += "<ul>"
          for (var i in results) html += "<li>" + results[i].Name + " " +results[i].Surname + "</li>";
          html += "</ul>"
          res.end(html);
        })
        .catch(function(err){
          console.log("Promise rejection error: "+err);
          res.end("<h1>ERROR</h1>")
        })
    }
}
}).listen(3001)
gtugnolo
  • 431
  • 5
  • 4
  • Cool. I've done this with another script, but when I try to move from console.log of results to putting them in an array, I get nothing. What's the solution when I don't want it going to console? – Ken Ingram Jun 27 '18 at 22:22
  • @KenIngram you should post your code: what are you trying to do? The `render` function I posted gets the array as parameter, so `results` is your array of objects. If you print it directly you'll get something like `[ RowDataPacket { Name: 'James' }, RowDataPacket { Name: 'Mary' } ]`. You can also access array properties like `results.length`. – gtugnolo Jun 29 '18 at 09:20
  • I was able to get some help on the Promises. Now that I understand it, your example makes complete sense. – Ken Ingram Jul 29 '18 at 20:11
  • Look at this many moons later, I've had time to work with Promises and async/await. So your solution sinks in a lot more. – Ken Ingram Jan 02 '21 at 05:52
  • Considering my understanding at the time, this answer was too complicated for me to grasp the logic. I understand better now, and there is a much simpler way to explain what's happening in my question. – Ken Ingram Mar 13 '21 at 22:27
13

Five years later, I understand asynchronous operations much better. Also with the new syntax of async/await in ES6 I refactored this particular piece of code:

const mysql = require('mysql2') // built-in promise functionality
const DB = process.env.DATABASE
const conn = mysql.createConnection(DB)

async function getInfo(data){
  var sql = "SELECT a from b where info = data"
  const results = await conn.promise().query(sql)
  return results[0]
}

module.exports = {
  getInfo
}

Then, where ever I need this data, I would wrap it in an async function, invoke getInfo(data) and use the results as needed.

Ken Ingram
  • 1,538
  • 5
  • 27
  • 52
1

This was a situation where I was inserting new records to a child table and needed the prent record key, based only on a name.

This was a good example of understanding the asynchronous nature of node.

I needed to wrap the all the code affecting the child records inside the call to find the parent record id.

I was approaching this from a sequential (PHP, JAVA) perspective, which was all wrong.

Ken Ingram
  • 1,538
  • 5
  • 27
  • 52
1

Easier if you send in a promise to be resolved e.g

function get_info(data, promise){

      var sql = "SELECT a from b where info = data";

      connection.query(sql, function(err, results){
            if (err){ 
              throw err;
            }
            console.log(results[0].objid); // good
            stuff_i_want = results[0].objid;  // Scope is larger than function
            promise.resolve(results[0].objid);
    }
}

This way Node.js will stay fast because it's busy doing other things while your promise is waiting to be resolved

Epirocks
  • 480
  • 4
  • 11
  • Thanks. I've been trying to implement Promises. I am stuck at converting the returned promise into usable data. I understand the concept, however, how to complete the technical implementation is eluding me. – Ken Ingram Sep 13 '17 at 22:08
  • 1
    db.findOne(272).then((user) => { console.log("Find user in location strategy", user[0]); return done(null, user[0]); }).catch(error => { return done(err); }); – Epirocks Sep 13 '17 at 23:34
  • I keep looking at this. It's an answer, but it assumes I understand how to make sense of it. I figured out Promises, but his snippet never actually helped. I needed a simpler breakdown. Neurodiverse. – Ken Ingram Dec 28 '18 at 04:22
  • 1
    Just over 2 years later and this solution makes more sense to me. But I'm curious about the function signature. Are you saying to invoke the function like this : get_info(data, new Promise())? I'm thinking async/await may fit the bill at this point. – Ken Ingram Jan 02 '21 at 05:54
  • 1
    You're right it's an odd way to do it. get_data should just return the new promise itself then it could be chained. Like you I gained more experience with it as time went on. – Epirocks Jan 09 '21 at 00:18
0

I've been working on this goal since few weeks, without any result, and I finally found a way to assign in a variable the result of any mysql query using await/async and promises.

You don't need to understand promises in order to use it, eh, I don't know how to use promises neither anyway

I'm doing it using a Model class for my database like this :

class DB {
    constructor(db) {
        this.db = db;
    }


    async getUsers() {
        let query = "SELECT * FROM asimov_users";
          return this.doQuery(query)
    }

    async getUserById(array) {
      let query = "SELECT * FROM asimov_users WHERE id = ?";
      return this.doQueryParams(query, array);
    }


    // CORE FUNCTIONS DON'T TOUCH
    async doQuery(queryToDo) {
        let pro = new Promise((resolve,reject) => {
            let query = queryToDo;
            this.db.query(query, function (err, result) {
                if (err) throw err; // GESTION D'ERREURS
                resolve(result);
            });
        })
        return pro.then((val) => {
            return val;
        })
    }
    async doQueryParams(queryToDo, array) {
      let pro = new Promise((resolve,reject) => {
        let query = queryToDo;
        this.db.query(query, array, function (err, result) {
            if (err) throw err; // GESTION D'ERREURS
            resolve(result);
        });
      })
      return pro.then((val) => {
        return val;
      })
    }
}

Then, you need to instantiate your class by passing in parameter to constructor the connection variable given by mysql. After this, all you need to do is calling one of your class methods with an await before. With this, you can chain queries without worrying of scopes.

Example :

connection.connect(function(err) {
    if (err) throw err;
    let DBModel = new DB(connection);

    (async function() {
        let oneUser = await DBModel.getUserById([1]);
        let allUsers = await DBModel.getUsers();

        res.render("index.ejs", {oneUser : oneUser, allUsers : allUsers});
    })();

});

Notes :

  • if you need to do another query, you just have to write a new method in your class and calling it in your code with an await inside an async function, just copy/paste a method and modify it
  • there are two "core functions" in the class, doQuery and doQueryParams, the first one only takes a string as a parameter which basically is your mysql query. The second one is used for parameters in your query, it takes an array of values.
  • it's relevant to notice that the return value of your methods will always be an array of objects, it means that you'll have to do var[0] if you do a query which returns only one row. In case of multiple rows, just loop on it.
  • This is an interesting implementation. Looks simple enough. I eventually found a solution that uses PHP and NODE, but I like the way you have done this. I understand async/Promises better now. – Ken Ingram Jan 24 '20 at 20:34
  • 1
    If you want to see this in a real app, take a look to the two files here https://github.com/Oskar-Ardolo/asimov/tree/master/core Here, you can find how it gradually becames even more simple when the project is growing – Vincent ROUILLARD MELTZ Jan 25 '20 at 00:29
  • Oh. I like this Vincent. It seems more elegant than I would have done. Great example. – Ken Ingram Jan 25 '20 at 23:54