1

I have a function to retrieve a list of UserID's from a mysql database.

function GetUsers(callback) {
  UpdateLogFile('Function Call: GetUsers()')
  var users = []
  Database.execute( connectionStr,
    database => database.query('select UserID from Users')
    .then( rows => {
      for (let i = 0; i < rows.length; i++){
        users.push(rows[i].UserID)
      }
      return callback(users)
    })
  ).catch( err => {
    console.log(err)
  })
}

For Reference:

Database class which came from here

const mysql = require( 'mysql' )
class Database {
  constructor( config ) {
    this.connection = mysql.createConnection( config )
  }
  query( sql, args ) {
    return new Promise( ( resolve, reject ) => {
      this.connection.query( sql, args, ( err, rows ) => {
        if ( err )
          return reject( err )
        resolve( rows )
      })
    })
  }
  close() {
    return new Promise( ( resolve, reject ) => {
      this.connection.end( err => {
        if ( err )
          return reject( err )
        resolve()
      })
    })
  }
}

Database.execute = function( config, callback ) {
  const database = new Database( config )
  return callback( database ).then(
    result => database.close().then( () => result ),
    err => database.close().then( () => { throw err } )
  )
}

After hours of learning about promises and callbacks, I was finally able to get GetUsers() to at least work and return what I'm looking for. However, I seem to only be able to use it as such:

GetUsers(function(result){
    // Do something with result
})

But I would really like to be able to have a traditional return statement in the function so that I could use it like this: var users = GetUsers(). I have seen posts saying that this is impossible due to the nature of asynchronous functions but I am still hopeful since I would really like to be able to avoid callback hell. I tried the code below but "users" just results as undefined after execution. So, my main goal is to be able to get the return value from GetUsers() without chaining callbacks together since I have other functions that behave similarly. Is this possible?

var users
GetUsers(function(result){
    users = result
})
console.log(users)
Jeremy
  • 45
  • 6

2 Answers2

1

This is a very confusing topic, and it took me a while to really understand why what you are asking simply is not possible (at least, in the exact way you are asking). For the examples I will using python Django and Node.js to compare.

Sync

def synchronous():
    print('foo') //this will always print first
    print('bar')

def getUsers():

    with connection.cursor() as cursor:
        cursor.execute('SELECT * FROM USERS')  //this query is executed
        users = cursor.fetchall()

        print('foo') //this doesn't trigger until your server gets a response from the db, and users is defined
        print(users)

Async

function asynchronous() {
    console.log('foo'); //this will also always print first
    console.log('bar');
}

function getUsers() {
   var connection = mysql.createConnection(config);
   connection.query('SELECT * FROM USERS', function(error, users) { //this is a "callback"
     console.log(users); //this will print
     //everything inside of here will be postponed until your server gets a response from the db

   });
   console.log('foo') //this will print before the console.log above
   console.log(users); //this will print undefined
   //this is executed before the query results are in and will be undefined since the "users" object doesn't exist yet.
}

A callback is simply the function that your server is supposed to run once you get a response. We typically use the actual word "callback" like this:

function getUsers(callback) {
   var connection = mysql.createConnection(config);
   connection.query('SELECT * FROM USERS', function(error, users) { 
   if (error) throw error; //always do your error handling on the same page as your query.  Its much cleaner that way

   callback(users) //server asks what to do with the "users" object you requested
   });
}

Now on somewhere else on your server:

getUsers(function(users) {// the callback gets called here
  console.log(users); //do what you want with users here
});

The getUsers function takes some other function (ie a callback) as its argument and executes that function after you perform your query. If you want to do the same thing without using the word "callback", you can use an await/async function like fsociety, or you explicitly write out your code and not make functions that take other functions as their arguments.

This is functionality identical to the code from above:

var connection = mysql.createConnection(config);
connection.query('SELECT * FROM USERS', function(error, users) { 
if (error) throw error;
console.log(users); 
});

Callback hell is inevitable, but it really Isn't too bad once you get the hang of it.

Lord Elrond
  • 13,430
  • 7
  • 40
  • 80
0

Use an async-await function instead.

async function GetUsers(callback) {
try {  
     UpdateLogFile('Function Call: GetUsers()')
     var users = []
     let rows = await Database.execute( connectionStr,
     database => database.query('select UserID from Users')

     for (let i = 0; i < rows.length; i++){
        users.push(rows[i].UserID)
      }
      return callback(users)

   } catch(err) {
    console.log(err)
  }
}

Hope this helps!

Faisal Arshed
  • 543
  • 1
  • 4
  • 14
  • 1
    Thanks! I was able to use your code and help from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function to make it work! Now the function returns a promise in a way that I'm able to use the function like `var users = await GetUsers()` and the code will wait for the result as if the function was synchronous – Jeremy Mar 03 '19 at 23:31