Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to await for a function or a database query? And then process results and finally send them back

I have this scenario:

app.get('/', async function (request, response) 
{
     await foo(DbConnection).then((result) => console.log("response ready"));
})

let foo = async function (DbConnection)
{
    const myQuery = "SELECT * FROM my_table";

    DbConnection.query(myQuery, async function(err, results) {
       console.log("query ready");
       await bar(results).then((response) => console.log("bar done"))
    })
    return; // go back to app.get() to send stuff to client
}

let bar = async function (results)
{   
    console.log("inside bar");
    await ThirdPartyAPI(result).then((value)) => return value);
}

Briefly:

  • I receive GET request from client

  • I call foo() which query data base and apply functions on results

  • I process results using third party API which takes time to finish

  • I send final results back to client

I am expecting to see:

query ready -> inside bar -> bar done -> response ready

But I instead see:

response ready -> query ready -> inside bar -> bar done

Client is receiving undefined since nothing was ready when response.send()

What could be the issue?

like image 789
Khalil Khalaf Avatar asked Jan 29 '23 18:01

Khalil Khalaf


1 Answers

The main problem in your code is that you're mixing async/await syntax and callbacks. Use async function only when you want to do something with the result of async call inside this function.

Also that, specific to your case, you need to promisify connection.query() - create a promise. See below the correct pattern with a working code:

app.get('/', async (request, response) => {
    // waiting for the result of foo
    let result = await foo();
    console.log("response ready");
});

let foo = async () => {
    const myQuery = "SELECT * FROM my_table";

    // getting the result of the query
    let results = await new Promise((resolve, reject) => connection.query(myQuery, (err, results) => {
      if (err) {
        reject(err)
      } else {
        resolve(results);
      }
    }));
    console.log("query ready");
    // call bar and waiting the result
    let res = await bar(results);
    console.log("bar done");
    // return resolved promise
    return res;
}

let bar = (results) => {   
    console.log("inside bar");
    // just return a promise, we don't need the result here
    return ThirdPartyAPI(result);
}

Explanation about using async/await. The power of async/await is that it lets you write asynchronous code using synchronous language constructs. But that doesn't mean that all functions should be marked as async. async wraps a function into a promise, if the function executes synchronous code, this wrapping can cause an error (you expect the string for example, but function returns a resolved promise). Another example when the function executes async code, but doesn't use intermediate result, in this case it's enough just return the result of async call.

You should use async/await when:

  • The function executes sync code, and you want to wrap the result into a promise (for creating a promise chain).
  • The function executes async code and you need an intermediate result.
  • The function executes async code and you don't need an intermediate result, but you want execute code in series (for processing each item in array for example).

Let's consider some examples:

1.sync function, validates userId. validateParams returns true/false, validateParams2 throws an error if userId is undefined. validateParams3 returns a promise, and can be used for creating a promise chain: validateParams3().then(...).

let validateParams = (userId) => {
  return userId;
}

let validateParams2 = (userId) => {
  if (!userId) {
    throw new Error('userId is undefined');
  }
}

let validateParams3 = (userId) => {
  if (!userId) {
    return Promise.reject(new Error('userId is undefined'));
  }
  return Promise.resolve();
}

2.async function, doesn't use intermediate result. getUser returns a pending promise (when userId is valid) or fulfilled promise (when userId is undefined). It should return a promise, because getUser can be used for starting a promise chain: createUser().then(...). getUser2 and getUser3 do the same thing, return a promise. The promise is requred here to avoid getting unhandledError error, because validateParams2 can throw an error. getUser2 makred as async (to create a promise automatically), and getUser3 returns a promise.

let getUser = (userId) => {
  if (validateParams(userId)) {
    return db.getUserById(userId);
  }
  return Promise.resolve();
}

let getUser2 = async (userId) => {
  validateParams2(userId);
  return db.getUserById(userId);
}

let getUser3 = (userId) => {
  return Promise
    .resolve(userId)
    .then(validateParams2)
    .then(() => db.getUserById(userId);
}

3.async function, uses an intermediate result:

let updateUser = async (userId, userData) => {
  let user = await getUser(userId);
  _.extend(user, userData);

  return db.saveUser(user);
}

The functions above can be used this way:

// with async
let fn = async (userId, userData) => { 
  try {
    let user = await updateUser(userId, userData);
    console.log(user);
  }
  catch (err) {
    console.log(err);
  }
}

// without async
let fn2 = (userId, userData) => { 
  updateUser(userId, userData)
    .then(user => console.log(user))
    .catch(err => console.log(err))
  }
}
like image 192
alexmac Avatar answered Feb 01 '23 07:02

alexmac