Async/Await in JS

ECMAScript 2017 brought in syntactic sugar on top of Promises in JavaScript in form of async and await. Async/await provides a special syntax to work with promises in a more comfortable fashion. They allow us to write asynchronous Promise-based code in a synchronous manner.

Before proceeding with async/await you need to understand Promises. You can head over to Promises in JS article to read and learn about promises.

Async functions

The async function declaration defines an asynchronous function. An asynchronous function is a function which operates asynchronously via the event loop.

Keyword async can be placed before a function:

async function f() {
    // function-body
}

An “async” function always returns a promise. If the returned value is non-promise then JavaScript wraps it in an implicit Promise.

Example:

async function f() {
    return 1;
}

f().then(result => {
    console.log(result)
})

// Output: 1

Async function returns a Promise which will be resolved with the value returned by the async function or rejected with an uncaught exception thrown from within the async function.


Await

The await operator is used to wait for a Promise. It can only be used inside an async function.

let value = await promise

The await expression causes the async function to pause execution until the Promise is settled.
The value of await expression is that of resolved promise. If Promise is rejected, await throws the rejected value.

Example from MDN:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function resolveAfter2Seconds(x) { 
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x)
    }, 2000)
  })
}

async function f1() {
  var x = await resolveAfter2Seconds(10); //wait till promise is resolved 
  console.log(x); // 10
}
f1();

// Prints '10' after 2 seconds

The function f1 execution pauses at line 10 and resumes when the promise is settled. The variable x has the result of the resolved promise that is, 10.

Await is a more polished syntax of getting promise result as compared to Promise.then. We can chain promises more elegantly with await than Promise.then().

Insted of this:

doAsync1.then(function(result) {
  return doAsync2(result)
})
.then(function(result) {
  return doAsync3(result)
})
.then(function(result) {
  console.log('Got the final result')
})
.catch(failureCallback);

We can do this:

async function f() {
    let v1 = await doAsync1()
    let v2 = await doAsync2(v1)
    let v3 = await doAsync3(v2)

    console.log('Got the final result')
}
f()

Await won’t work in top-level code. It can also be used inside an async function.

Error Handling

If a promise is fulfilled then await returns its result. But in the case of rejection, it throws an error. We can use simple try...catch statement to catch the error.

var f = async function() {
  try {
    let v = await Promise.reject(new Error("Invalid Statement"))
  } catch(err) {
    console.log(err.message)
  }
}()

// Output: "Invalid Statement"


Handling rejected Promise without try block.

var f = async function() {
  let result = await Promise.reject(new Error("Invalid Statement"))
}
// result will be undefined if the promise is rejected

f().catch(err => {
  console.log(err.message)
})

// Output: "Invalid Statement"


Example

Callbacks are not interchangeable with Promises. This means that callback-based APIs cannot be used as Promises.

But we can wrap a library using callback-based API inside a Promise.

Example:

const request = require('request')

function get_response() {
    return new Promise((resolve, reject) => {
        request.get(options, (err, res, body) => {
            if(err) 
                reject(err)
            else
                resolve(JSON.parse(body))
        })
    })
}

request library uses callbacks. Here we wrap it inside a function returning Promsie. The function is then responsible for calling resolve when it’s done or reject if there are errors.

Now we can use .then() or await on the returned Promise.

var options = {
    url: 'https://api.github.com/users/rishabhc32',
    headers: {
        'User-Agent': 'request'
    }
}

get_response()
.then((result) => {
    console.log(result)
})
.catch((err) => {
    console.log('Error occured')
})
let another_response = async function() {
    options.url = 'https://api.github.com/users/mittalprince'

    let result = await get_response()
    console.log(`login id: ${result.login} \nid: ${result.id}`)

    options.url = 'malformed-url'
    
    try {
        let result = await get_response()
    } catch(err) {
        console.log(`Error: ${err.message}`)
    }
 }()  


Further reading