Pascal Claes
Pascal Claes

Reputation: 161

How to make async await work for a function that calls a database

I have an async function processWaitingList() that needs to wait for the result of another function findOpenVillage() that gets it's data from my database.

I'm getting the following error and do not understand why:

(node:2620) UnhandledPromiseRejectionWarning: TypeError: Cannot read property '0' of undefined at processWaitingList (xx\server.js:151:36) at process._tickCallback (internal/process/next_tick.js:68:7) (node:2620) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)

I've read everything I can find on promises, callbacks, async and await, but I just cannot figure out how these work.

function slowServerLoop() {
    processWaitingList();
}
setInterval(slowServerLoop, 500);

async function processWaitingList() {
    let openVillage = await findOpenVillage();
    villageId = openVillage[0];
    openSpots = openVillage[1];
    addPlayerToVillage(waitingList[0], villageId, openSpots);
}

function findOpenVillage() {
    con.query(`SELECT id, maxVillagers FROM villages WHERE status='0'`, function (err, result, fields) {
        if (err) throw err;
        if (result.length === 1) {
            let villageId = result[0].id;
            let maxVillagers = result[0].maxVillagers;
            con.query(`SELECT COUNT(*) as villagerCount FROM villagers WHERE villageId='${villageId}'`, function (err, result, fields) {
                if (err) throw err;
                let villagerCount = result[0].villagerCount;
                let openSpots = maxVillagers - villagerCount;
                return [villageId, openSpots];
            });
        }
    });
}

Upvotes: 2

Views: 528

Answers (3)

libik
libik

Reputation: 23029

there is important thing about await. If you put it behind some promise, it will await the promise to be resolved and then return the value to your code.

In your case, you are not returning promise, so there is nothing to be awaited.

When you need to combine promises with callbacks (async/await is just extension over promise chains), one of way is to wrap callback inside new promise.

In your case

return new Promise((resolve, reject) =>
{
con.query(`SELECT id, maxVillagers FROM villages WHERE status='0'`, function (err, result, fields) {
        if (err) { return reject(err); }
        if (result.length === 1) {
            let villageId = result[0].id;
            let maxVillagers = result[0].maxVillagers;
            con.query(`SELECT COUNT(*) as villagerCount FROM villagers WHERE villageId='${villageId}'`, function (err, result, fields) {
                if (err) { return reject(err); }
                let villagerCount = result[0].villagerCount;
                let openSpots = maxVillagers - villagerCount;
                resolve([villageId, openSpots]);
            });
        }
    });
}
   

Also remember that you need to return it (its already in code ^^ )

Upvotes: 2

Explosion Pills
Explosion Pills

Reputation: 191729

findOpenVillage does not return a Promise. You need it to return a Promise in order to use async/await.

If possible, you should see if con.query has the ability to return a Promise. There is the mysql-promise library that you could use, among others. You could also use the Promise constructor yourself or the built in util.promisify. Let's use that last option as an example:

const util = require('util');

...
async function findOpenVillage() {
  const query = util.promisify(con.query);
  const maxResult = await query(`SELECT id, maxVillagers FROM villages WHERE status = '0'`);
  if (maxResult.length === 1) {
    const villageId = result[0].id;
    const maxVillagers = result[0].maxVillagers;
    const countResult = await query(
      `SELECT COUNT(*) as villagerCount FROM villagers WHERE villageId = ?`,
      villageId
    );
    const villagerCount = result[0].villagerCount;
    const openSpots = maxVillagers - villagerCount;
    return [villageId, openSpots];
  }
}

I've made this an async function so you could use await, but using promises with .then would also be reasonable. If you did that, you would have to return the promise.


Your original code was using string interpolation instead of parameterized queries. This makes the query vulnerable to injection. I have corrected this in my answer as well.

Upvotes: 2

Shahzeb Khan
Shahzeb Khan

Reputation: 3642

You need to access the code after the promise is fullfilled. Your function findOpenVillage() must return a promise object. You need to access the values in then. It means that you will be accessing the values after the promise is fullfilled.

openVillage.then(function(response){
villageId = openVillage[0];
    openSpots = openVillage[1];
    addPlayerToVillage(waitingList[0], villageId, openSpots);
});

Upvotes: -1

Related Questions