Skip to content

Method returns undefined the first time it is called

I have a method that selects distinct values from a database as shown below:

function displayCategories(res, req) {
    query = `SELECT DISTINCT name FROM product_category;`;
    connection.query(query, function (err, rows) {
        if (err) {
            console.log(err);
            res.render("home");
            throw err;
        } else {
            session = req.session;
            session.categories = rows[0];
        }
    });
}

I then have a button with the method POST and action /categories

The displayCategories is called when the button is clicked as follows:

router.post('/categories', function (req, res) {
    displayCategories(res, req);
    if (session.categories === undefined) {
        console.log("categories is undefined");
    } else {
        console.log("categories is defined");
        console.log(session.categories);
    }
})

I added some console logs for test purposes. The issue I am having is that the first time I click the button, it returns undefined. Each time I click it again, it prints the correct data for session.categories as shown below:

categories

Is there a simple fix for this issue?

Answer

The code is calling a displayCategories as if it were synchronous, but it is running asynchronous code with the callback.

There are multiple possible solutions for that but one of them would be to use Promises, like the following:

const displayCategories = (res, req) => new Promise((resolve, reject) => {
    // you are not declaring query in this scope, that makes it global
    query = `SELECT DISTINCT name FROM product_category;`
    connection.query(query, function (err, rows) {
        if (err) {
            console.error(err)
            res.render("home")
            reject(err)
        } else {
            session = req.session
            session.categories = rows[0]
            resolve()
        }
    })
})

And the other part with an async function

router.post('/categories', async function (req, res) {
    await displayCategories(res, req);
    if (session.categories === undefined) { // session is not declared
        console.log("categories is undefined");
    } else {
        console.log("categories is defined");
        console.log(session.categories); // session is not declared
    }
})

But that’s just to make your issue go away, if you want to improve the code even further you can just keep the responsibility of dealing with request and response with the controller action and just use the other function to get the data you want, isolation its responsibility:

const getCategories = () => new Promise((resolve, reject) => {
  const query = `SELECT DISTINCT name FROM product_category;`
  connection.query(query, (err, rows) => {
    if (err) return reject(err)

    resolve(rows)
  })
})

router.post('/categories', async function (req, res) {
  try {
    req.session.categories = await getCategories();

    if (req.session.categories === undefined) {
        console.log("categories is undefined");
    } else {
        console.log("categories is defined", req.session.categories);
        console.log();
    }
  } catch(e) {
    console.error(e)
    res.render("home")
  }
})