DEV Community

loading...

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

Corey Cleary
Tech Lead primarily working with JavaScript and Node.js
Originally published at coreycleary.me ・4 min read

Originally published at coreycleary.me. This is a cross-post from my content blog. I publish new content every week or two, and you can sign up to my newsletter if you'd like to receive my articles directly to your inbox! I also regularly send cheatsheets and other freebies.

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

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!

I'm writing a lot of new content to help make Node and JavaScript easier to understand. Easier, because I don't think it needs to be as complex as it is sometimes. If you enjoyed this post and found it helpful here's that link again to subscribe to my newsletter!

Discussion (2)

Collapse
anduser96 profile image
Andrei Gatej

Thank you for sharing this!

As far as I can understand, the code above will process the requests sequentially, which means that if each request takes 1s to be solved and you have 8 of these requests, the amount of time you’d have to wait is 8s.
Please correct me if I’m wrong!

I just wanted to make sure that I really understand what’s going on. Thanks!

Collapse
ccleary00 profile image
Corey Cleary Author

Yes, exactly. They will be in series (sequentially). In some scenarios you'll want to call them in series, and others you'll want them concurrently, just depends on the situation.

It's funny you bring this up - my next post is covering this exact topic (concurrent vs. in series async calls). I'll be posting it early next week, but the short of it is that, if you want the async functions to execute concurrently, use Promise.all