Getting started with Async/Await in JavaScript

Getting started with Async/Await in JavaScript
Getting started with Async/Await in JavaScript

Inspired by the Zeit team’s post on the subject, my team at PayPal recently migrated our main server-side codebase to use Async/Await. I’m excited to share with you some of things we learned along the way.

Let’s start with some terminology:

  • Async function
  • Await keyword

People usually say async/await which is lovely and nice really, but you should know that they aren’t the same thing. There are _async functions_and there is the await keyword. They are certainly tied together in some ways, but async functions in particular can be used without await. How is that?

Async Functions Return a Promise

When you create a function with the async keyword that function will always return a Promise. When you return inside your async function it wraps your value in a Promise.

// here is an async function
async function getNumber() {
  return 4 // actually returns a Promise
}

// the same function using promises
function getNumber() {
     return Promise.resolve(4)
}

Even if your code throws inside an async function it won’t automatically bubble up, instead it will return a rejected promise

Async Functions are the only Place where you can use Await

In addition to converting your returns into a Promise, an async function is also special in that it’s the only place where you can use the await keyword.*

What is the await keyword? Await lets you pause the execution of an async function until it receives the results of a promise. This lets you write async code that reads in the order that it’s executed.

// async function to show user data
async function displayUserData() {
    let me = await fetch('/users/me')
    console.log(me)
}

// promise-based equivalent
function displayUserData() {
    return fetch('/users/me').then(function(me) {
        console.log(me)
    })
})

Await allows you to write asynchronous code with no callbacks at all. This makes your code much more readable. And await works with any promise, not just promises created by async functions.

Error Handling in Async Functions

Because an async function is a Promise, when you throw inside of an async function it’s swallowed up and returned as a rejected Promise.

// basic error handling with async functions
async function getData(param) {
   if (isBad(param)) {
      throw new Error("this is a bad param")
   }
   // ...
}

// basic promise-based error handling example
function getData(param) {
   if (isBad(param)) {
      return Promise.reject(new Error("this is a bad param"))
   }
   // ...
}

If you are using await to call the Promise you can wrap it in try/catch or you’ll need to add a catch handler to the returned Promise.

// rely on try/catch around the awaited Promise
async function doSomething() {
    try { 
        let data = await getData()
    } catch (err) {
        console.error(err)
    }
}

// add a catch handler
function doSomething() {
    return getData().catch(err => {
        console.error(err)
    })
}

Promise errors usually bubble up to their parent, so you usually only need that try/catch on your top-level Promise.

Putting it all Together

Taking advantage of the error handling properties of promises and the concise syntax of async functions can yield some powerfully simple results.

In this simplified example you can see how one might take advantage of the inherent error handling capabilities async functions to simplify error handling in an Express app

// catch any errors in the promise and either forward them to next(err) or ignore them
const catchErrors = fn => (req, res, next) => fn(req, res, next).catch(next)
const ignoreErrors = fn => (req, res, next) => fn(req, res, next).catch(() => next())

// wrap my routes in those helpers functions to get error handling
app.get('/sendMoney/:amount', catchErrors(sendMoney))

// use our ignoreErrors helper to ignore any errors in the logging middleware
app.get('/doSomethingElse', ignoreErrors(logSomething), doSomethingElse)

// controller method can throw errors knowing router will pick it up
export async function sendMoney(req, res, next) {
  if (!req.param.amount) {
     throw new Error('You need to provide an amount!')  
  }
  await Money.sendPayment(amount) // no try/catch needed
  res.send(`You just sent ${amount}`)
}

// basic async logging middleware
export async function logSomething(req, res, next) {
    // ...
    throw new Error('Something went wrong with your logging')
    // ...
}

On my team at PayPal we usually handle errors with next(err). However with async/await we can simply throw errors anywhere in the code and the router will forward them to the next function provided by Express**.** This is a huge simplification.

Moving from callbacks to promises and async/await has condensed error handling in our app and will improve comprehension for our more complicated code paths. It took me a couple of hours to migrate most of our routes from plain callbacks to this new approach. Really the only thing you need to get started is a solid knowledge of Promises and an understanding of how to setup babel.

I eagerly await hearing your experiences with these newfangled functions and believe they are going to be one of my favorite tools in the JavaScript toolbox moving forward.

**Recommended Courses: **

JavaScript Fundamentals core concepts
https://goo.gl/W7mi81

Get started with Javascript Development in Just a Few Hours.
https://goo.gl/LgzUV7

ReactJS Course: Learn JavaScript Library Used by Facebook&IG
https://goo.gl/YNHGQx

Full Stack Angular 5 and NodeJS 9 Developer
https://goo.gl/nZu3AM

Suggest:

Async/Awaiting Production in Python

JavaScript Programming Tutorial Full Course for Beginners

Learn JavaScript - Become a Zero to Hero

Machine Learning Zero to Hero - Learn Machine Learning from scratch

Top 4 Programming Languages to Learn In 2019

Javascript Project Tutorial: Budget App