Simplify code with promises and async/await

Why promises are needed

Recently, I’ve been trying my hand at React and react-admin to build an admin web site. Since it’s an admin web site, there are many calls to the API backend, and consequently, many queries to the database. Sometimes it’s necessary to query different tables in database and work with the results of those queries. Now, with other programming languages like php, everything runs sequentially so I didn’t have to worry about asynchronous calls. With javascript and its callbacks and thens, I had to make sure I had all the data before passing the data back to the frontend.

In my sequential process trained mind, I thought I had to just chain everything one after another. The following shows what I tried using knex SQL query builder.

function queryDB() {
  knex.select('field1', 'field2', 'field3')
    .from('table1')
    .then(function(rows) {
      if (rows.length > 0) {
        const valueFromQuery1 = rows[0];
        knex.select('field1', 'field2', 'field3')
          .from('table2')
          .where('field1', valueFromQuery1)
          .then(function(rows) {
            if (rows.length > 0) {
              const valueFromQuery2 = rows[0];
              knex.select('field1', 'field2', 'field3')
                .from('table3')
                .then(function(rows) {
                  if (rows.length > 0) {
                    const valueFromQuery3 = rows;
                    ...
                  }
                });
            }
          });
      }
  });
}

That is messy. The code is hard to follow, not very performant, and just FEELS ugly. I was sure there’s a better way. So I scoured the web and gathered information and knowledge which led me to promises. I tried converting my code using promises, but returning resolves, thening another then, promises within a callback didn’t seem like much of an improvement.

Async/await makes it simpler

Then I also read about async/await. Ah… so it’s ES2017 standard you say? AND it simplifies your code? AND it improves code readability? Count me in!

After wrestling with untangling this mess of a code, I THINK I finally got an inkling of an idea of async/await concept.

async function queryDB() {
  let rows = await knex.select('field1', 'field2', 'field3')
    .from('table1');
  const valueFromQuery1 = rows.length > 0 ? rows[0] : null;
  rows = await knex.select('field1', 'field2', 'field3')
    .from('table2')
    .where('field1', valueFromQuery1);
  const valueFromQuery2 = rows.length > 0 ? rows[0] : null;
  rows = await knex.select('field1', 'field2', 'field3')
    .from('table3');
  const valueFromQuery3 = rows.length > 0 ? rows : null;
}

Promise is still there

Now the code is much shorter, easier to read and follow. The only problem is the queries will sequentially. So if the second query takes a long time to complete, third query will have to wait until it’s finished. We might not want that. In order to run both the second query and the third query in parallel, we use Promise.all.

 async function queryDB() {
  let rows = await knex.select('field1', 'field2', 'field3')
    .from('table1');
  const valueFromQuery1 = rows.length > 0 ? rows[0] : null;
  let [rows2, rows3] = await Promise.all([
    runQuery2(valueFromQuery1), runQuery3()
  ]);
  const valueFromQuery2 = rows2.length > 0 ? rows2[0] : null;
  const valueFromQuery3 = rows3.length > 0 ? rows3 : null;
}

async function runQuery2(value) {
  return await knex.select('field1', 'field2', 'field3')
    .from('table2')
    .where('field1', valueFromQuery1);
}

async function runQuery3(value) {
  return await knex.select('field1', 'field2', 'field3')
    .from('table3')
}

This version is a little longer due to turning queries into functions, but those functions will be reusable and execution will only last as long as the longest query. I’ve used knex query as an example, but any function in its place should work.

I had to read upon many examples to really grasp the concept of how asynchronous calls work in javascript beyond simple fetches and database queries. I’ve found Sitepoint’s example and Tania’s post the most helpful.

If I missed anything or if something can be improved, please let me know in the comments.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.