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!
Top comments (2)
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!
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