Applying the callback -> async/await conversion process to a real-world example

This is a follow-up to my post on the process for converting callbacks to Promises and to `async/await` functions

In that post I stuck to using setTimeout as an easy way to introduce some asynchronicity into the code. But I understand that for some people, they need more real-world examples to read and play around with in order to truly get the concept to click.

So that post was more about the process, and this one is more about implementation. In this post we'll skip promises and go directly from callback to async/await.

The callback version

Our scenario is that we need to:

  • loop over a list of book titles
  • for each one, make a request to the Openlibrary API with book title
  • get isbn from Openlibrary
  • insert isbn number and book title into 'books' table

If you want the complete code, as well as instructions for setting up the database locally so you can play around with this code, subscribe here:

Subscribe for the code and for more JS, Node and testing content!

Here's the code using callbacks, this is what we'll be converting:

const request = require('superagent')
const { Client } = require('pg')

const titles = ['Gullivers Travels', 'Gravitys Rainbow']

const getISBN = (bookTitle, callback) => {
  return request
    .get('http://openlibrary.org/search.json')
    .query({q: bookTitle})
    .end((err, res) => {
      if (err) return callback(new Error(`Error calling OpenLibrary: ${err}`))
      if (res.status === 200) {
        const parsed = JSON.parse(res.text)
        const first_isbn = parsed.docs[0].isbn[0]
        return callback(null, first_isbn)
      }
    }
  )
}

const getConnection = () => {
  return {
    host: 'localhost',
    database: 'books',
    password: null,
    port: 5432,
  }
}

const insert = (tableName, bookTitle, isbn, callback) => {
  const client = new Client(getConnection())
  client.connect()

  client.query(`INSERT INTO ${tableName} (bookTitle, isbn) VALUES ('${bookTitle}', '${isbn}');`, (err, res) => {
    if (err) callback(new Error(`Error inserting: ${err}`))
    else callback(null, res)
    client.end()
  })
}

// loop over titles
for (const bookTitle of titles) {
  // make request to openlib with book title
  // get isbn from openlib
  getISBN(bookTitle, (err, res) => {
    if (err) {
      console.log('Hit an error calling OpenLibrary API', err)
    } else {
      const isbn = res
      // add isbn number and book title to table
      insert('books', bookTitle, isbn, (err, res) => {
        if (err) {
          console.log('Hit an error inserting into table', err)
        } else {
          console.log(`${bookTitle}, ISBN: ${isbn} added to books table`)
        }
      })
    }
  })
}

Applying the process

Let's start applying the process.

We'll start with the getISBN function:

Then the insert function, for inserting into the database:

And now, the "main" function that executes our logic:

One thing to note here for this last bit of code, for the async/await version is that if there is an error in the getJSON function call, it will be caught by the catch(e) block and the function will exit. The insert function won't be called. We could have wrapped each await call in its own try/catch too, if we wanted to avoid this behavior. It just depends on the needs of the code/feature you're working on.

After - async/await

Here's the complete async/await version:

const request = require('superagent')
const { Client } = require('pg')

const titles = ['Gullivers Travels', 'Gravitys Rainbow']

const getISBN = async (bookTitle) => {
  let response

  try {
    const apiResponse = await request
      .get('http://openlibrary.org/search.json')
      .query({q: bookTitle})

    const parsed = JSON.parse(apiResponse.text)
    response = parsed.docs[0].isbn[0]
  } catch(e) {
    throw new Error(`Error calling OpenLibrary: ${e}`)
  }

  return response
}

const getConnection = () => {
  return {
    host: 'localhost',
    database: 'books',
    password: null,
    port: 5432,
  }
}

const insert = async (tableName, bookTitle, isbn) => {
  const client = new Client(getConnection())
  await client.connect()

  let res

  try {
    res = await client.query(`INSERT INTO ${tableName} (bookTitle, isbn) VALUES ('${bookTitle}', '${isbn}');`)
  } catch(e) {
    throw new Error(`Error inserting: ${e}`)
  }

  await client.end()
  return res
}

const run = (async () => {
  for (const bookTitle of titles) {
    try {      
      // make request to openlib with book title
      // get isbn from openlib
      const isbn = await getISBN(bookTitle)

      // add isbn number and book title to table
      await insert('books', bookTitle, isbn)
      console.log(`${bookTitle}, ISBN: ${isbn} added to books table`)
    } catch(e) {
      throw new Error(e)
    }
  }
})()

Wrapping up

If the first post didn't help things click for you, hopefully seeing an example like this one did.

The next time you need to convert callbacks, apply this process and reference the post here in order to more easily grasp how to move away from callbacks!

And if you found this helpful, and want to receive future posts, cheatsheets, etc directly to your inbox without having to remember to check back here, sign up to be added to the mailing list below. I'll send you the code from this post as well as setup instructions for PostgreSQL so you can call an actual database and play around with the code!

Subscribe for the code and for more JS, Node and testing content!

No spam ever. Unsubscribe any time.