DEV Community

Damien Cosset
Damien Cosset

Posted on

Asynchronous code with async/await

Introduction

I wrote about promises and generators being introduced in ES6. Another way to make asynchronous code look synchronous almost made it in ES6, but not quite: async/await. This functionality is built on top of promises. Let's take a look at it.

Syntax

The syntax is as follow: you must declare a function to be async:

const asyncFunction = async () => {
  // This is a good start
}

// or

const asyncFunction = async function(){
  // Old school function keyword? I like it!
}

Then, inside this async function, you can use the await keyword to tell the function it should wait for something:

const asyncFunction = async () => {
  const step1 = await fetchingData() // Wait for this

  const step2 = await savingData() // Then wait for that

  // Do something else
}

You can still keep your promises

I mentioned that async/await is build on top of promises. An async function returns a promise. This means you can call .then() and .catch() on them:

const fs = require('fs')

// promisify is a neat tool in the util module that transforms a callback function into a promise one
const { promisify } = require('util')
const writeFile = promisify(fs.writeFile)
const readFile = promisify(fs.readFile)

const writeAndRead = async () => {
  await writeFile('./test.txt', 'Hello World')
  const content = await readFile('./test.txt', 'utf-8')

  return content
}

writeAndRead()
  .then(content => console.log(content)) // Hello World

Ok, what is happening here?

  • We create an async function called writeAndRead.
  • The function has two await keywords: first, we wait for the function to write to the file test.txt
  • Second, we wait for the function to read the test.txt file we just wrote to.
  • We store that in a variable and return it
  • Because async functions return promises, I can use .then() after calling the writeAndRead() function.

Pretty sweet huh? We don't even need to specify a resolve() and reject() method anymore. Which brings me to the next point.

You are all the same errors to me <3

Let's assume a scenario where you have promises-based logic and non-promises-based logic ( synchronous and asynchronous ) in your code. You would probably handle errors this way:

const someComplicatedOperation = () => {
  try {
    // Blablabla do something
    db.getUsers()     //promise
    .then( users => {
      const data = JSON.parse( users )    // ===> What if this fail bro?
      console.log(data)
    })
    .catch( err => {
      console.log('You broke your promise!!!')
    })
  }
  catch( err ){
    console.log('I caught a error. But I only catch synchronous stuff here :(')
  }
}

That's right. The try/catch won't catch the JSON.parse error because it is happening inside a promise. A rejected promise triggers the .catch() method, but NOT the other catch. That's annoying, we have to duplicate code for catching errors. Well, that time is now over with async/await!

const allErrorsAreDeclaredEqualInTheEyesOfAsyncAwait = async () => {
  try {
    const users = await db.getUsers
    const data = JSON.parse( users )
    console.log(data)
  }
  catch( err ){
    console.log('All errors are welcomed here! From promises or not, this catch is your catch.')
  }
}

Clean, concise and clean, while being concise. Good old try/catch can handle all the errors we can throw.

How high can you stack them errors?

As developers, if there is one thing we love, it is an infinite amount of functions in an error stack. It is probably not a huge deal, but more like a nice thing to know when you work with async/await. Check it out:

const stackingAllTheWayToTheSky = () => {
  return usefulPromise()
    .then(() => usefulPromise())
    .then(() => usefulPromise())
    .then(() => usefulPromise())
    .then(() => usefulPromise())
    .then(() => usefulPromise())
    .then(() => {
      throw new Error('I can see my house from here!!')
    })
}

stackingAllTheWayToTheSky()
  .then(() => {
    console.log("You won't reach me.")
  })
  .catch(err => {
    console.log(err) // FEEL THE PAIN!
  })

  //Error: I can see my house from here!!
  //  at stackingAllTheWayToTheSky.then.then.then.then.then.then (index.js:50:11)

Now with async/await:

const debuggingMadeFun = async () => {
  await usefulPromise()
  await usefulPromise()
  await usefulPromise()
  await usefulPromise()
  await usefulPromise()
  throw new Error('I will not stack.')
}

debuggingMadeFun()
  .then(() => {
    console.log('Not here')
  })
  .catch(err => {
    console.log(err)
  })
  //Error: I will not stack
  // at debuggingMadeFun (index.js:47:9)

Ain't that much much more cleaner and easy to read?

Values in the middle

You probably wrote some code where you executed one operation and used that to execute a second one. Finally, you need those two values for the third and final operation. So, you may write something like that:

const withPromises = () => {
  return firstPromise()
    .then( firstValue => {
      return secondPromise( firstValue )
    })
    .then( secondValue => {
      return thirdPromise( firstValue, secondValue )
    })
}
// Or using Promise.all. It's a bit ugly, but the job is done

const withPromiseAll = () => {
  return firstPromise() 
    .then(firstValue => {
      return Promise.all([ firstValue, secondPromise(firstValue) ])
    })
    .then(([firstValue, secondValue]) => {
      return thirdPromise(firstValue, secondValue)
    })
}

Let's look at how much better it is with async/await:

const withAsyncAwait = async () => {
  const firstValue = await firstPromise()
  const secondValue = await secondPromise()
  return thirdPromise( firstValue, secondValue )
}

Do I need to say more?

Conclusion

Well, async/await is a very cool way to write asynchronous code in Javascript. You can try it out in Node.js because it is natively supported since version 7.6. Have fun!!

Top comments (4)

Collapse
 
math2001 profile image
Mathieu PATUREL • Edited

Quick tip: the async/await version of Promise.all:

async function main() {
  // NO! You're wait for one at a time, which isn't good

  let themes = await getThemes()
  let content = await getContent()

  // YES! You wait for both at the same time

  let themesPromise = getThemes() // this doesn't block anything
  let contentPromise = getContent() // this either

  let [themes, content] = [await themesPromise, await contentPromise]
}
main()
Collapse
 
luckymore profile image
不长肉

can't get you...

Collapse
 
robihood23 profile image
Robert Gogolan

Except the 3 first snippets, in the rest you forgot to declare the functions as async. As far as i know you cannot use await inside a function if it is not async.

I know that it was not intentional :D

Collapse
 
damcosset profile image
Damien Cosset

Damn, you're right. I edited the article. Thank you sir :)