DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for How to effectively utilize Promise in JavaScript using Async/Await
Emmanuel Fordjour  Kumah
Emmanuel Fordjour Kumah

Posted on

How to effectively utilize Promise in JavaScript using Async/Await

JavaScript supports three approaches to handling asynchronous operations.

The first approach involves using callback functions. In this approach, when an asynchronous operation completes, a callback function is invoked that was passed as an argument.

See the code below:

function callbackFn(){
  //execute when the async operation completes
}
// pass callbackFn as an argument
asyncOperation(callbackFn)
Enter fullscreen mode Exit fullscreen mode

Using this approach results in callback hell, which creates complex, difficult-to-read, and error-prone applications as you nest callbacks into callbacks.

To handle asynchronous operations effectively, JavaScript introduced promises to solve the issue with callback hell.

A Promise is an object that serves as a placeholder for the future result of an asynchronous operation and represents our operation's current state.

Upon completion of the asynchronous operation, we return a fulfilled promise object. In the promise object, we use the then() method to utilize the result.

The syntax is as below:

const promise = asyncOperation()
promise.then(result => console.log(result))
Enter fullscreen mode Exit fullscreen mode

Promises introduce a better improvement in handling asynchronous operations and solved the challenge of callback hell.

The only problem is, Promises require a series of then() to consume any returned Promise object, which increases the wordiness of our code.

async/await was introduced in ES2017 as a better approach to handling Promises

In this post, we will learn how to use async/await to utilize Promise.

By the end of this article, you will learn:

  • Why async/await is referred to as syntactic sugar
  • How to declare an async function
  • What is the async keyword
  • What is the await keyword
  • Difference between consuming Promises using .then() vs async/await
  • The benefits of using async functions
  • How to handle errors in an async function

Let's get started !

Understanding Syntactic Sugar

async/await is referred to as syntactic sugar to Promise, but what does this really mean? Syntactic sugar is syntax within a programming language implemented to make code easier to read or express.

Rather than using Promise Chaining, we can use async/await to write well-structured code

The code below looks well-structured and devoid of wordiness.

async function getSomeData(){
  const response = await fetch('https://jsonplaceholder.typicode.com/posts')
  const data = await response.json()
  console.log(data)
}
//call the asynchronous function
getSomeData()
Enter fullscreen mode Exit fullscreen mode

Understanding the Async keyword

The async keyword prompts the JavaScript engine that, we are declaring an asynchronous function.

Using the async keyword prior to a function automatically transforms it into a Promise. Meaning, the return value of the async function will always be a Promise.

If the returned value of any async function is not clearly a promise, it would be silently wrapped in a promise

See the code below :

async function someAsyncOps(){
}
//call the asynchronous function
console.log(someAsyncOps())
Enter fullscreen mode Exit fullscreen mode

See the code below:

async function someAsyncOps(){
}
//call the asynchronous function
console.log(someAsyncOps())
Enter fullscreen mode Exit fullscreen mode

The output of the code will be

AsyncAwait

  • There is no return value in the body of the function above. However, because of the async keyword used, the returned value was a Promise

To reiterate, using the async keyword prior to any function, the return value is a Promise. This promise will be settled with the value returned by the async function or rejected with an exception thrown from the async function

Syntax of the Async function

We define the async function using either the regular function declaration or an arrow function.

The syntax of the async function is as below:

//function declaration 
async function someAsyncOps(){
  await ... //some async operation goes here

}
//arrow declaration
const someAsyncOps = async ()=>{
  await ... //some async operation goes here

}
Enter fullscreen mode Exit fullscreen mode

Understanding the Await keyword

When an await expression is used, the execution of an async function is paused until a promise settles (fulfilled or rejected), returns the promised result, and then the execution resumes.

When resumed, the value of the await expression is that of the fulfilled promise.

The await keyword works only in the body of an async function. It prompts the JavaScript engine to wait for the asynchronous operation to complete before continuing with any statement below it in the function's body.

It works as a pause-until-done keyword, causing the JavaScript engine to pause code execution until the Promise is settled.

The syntax is as below:

//should be used inside an async function
let value = await promise //wait until promise is settled
Enter fullscreen mode Exit fullscreen mode

Examine the code below:

async function someAsyncOps(){
  //use Promise constructor
  let promise = new Promise((resolve, reject)=>{
    setTimeout(() => resolve('done'),4000)
  });
  let result = await promise; // (*) wait until promise settles
  console.log(result)

  console.log("Will resume execution when promise settled")
}
someAsyncOps()
Enter fullscreen mode Exit fullscreen mode

In the code above:

The function execution "pauses" at the line (*) waiting for the promise to settle. Using the setTimeout to stimulate a delay, the promise takes about 4 seconds to settle. Once the promise settles, we return the fulfilled value to the result variable. After, any statement below that line of code will be executed.

To emphasize, the await hangs the function execution until the promise settles, and then resumes it with the promised result.

It is used as an alternative to promise handling rather than the .then() method.

Inside the Async function's body

An async function's body can be thought of as being divided by zero or more await expressions.

Top-level code is executed synchronously and includes the first await expression (if there is one).

This way, an async function without an await expression will run synchronously. The async function will, however, always complete asynchronously if there is an await expression inside the function body

Run the code snippet below to understand

async function someAsyncOps(){
 console.log("Top level code will execute first ") //executed synchronously
  //use Promise constructor
  let promise = new Promise((resolve, reject)=>{
    setTimeout(() => resolve('done'),4000)
  });
  let result = await promise; //wait until promise settles, hence run asynchronousy
  console.log(result)

  console.log("Code below will resume execution now that the promise has settled")
}
//invoke the function
someAsyncOps()
Enter fullscreen mode Exit fullscreen mode

The output will be

AsyncAwait

Benefits of Async/Await

With async function, the JavaScript engine will pause any code execution when it encounters the await expression, and only resumes when the promise is fulfilled.

The code below uses the async function:

async function someAsyncOps(){
 console.log('Async/Await Readying...')
 let response =  await fetch('https://dummyjson.com/products/1')
 let result = await response.json() // pause code execution until promise is fufilled
 console.log(result)
 console.log('This will only execute when the promise is fulfilled')
}
someAsyncOps()
Enter fullscreen mode Exit fullscreen mode

The output will be:

AsyncAwait

However, the .then() method does not pause any code execution. Code below the promise chaining will execute before the promise is fulfilled

The code below uses Promise chaining

function getData(){
 console.log('Then method reading....')
   fetch('https://dummyjson.com/products/1').then((res)=> console.log(res))
   console.log('This will not pause, but will be executed before the promise is fulfilled');
}
getData()
Enter fullscreen mode Exit fullscreen mode

The output will be

AsyncAwait

If we utilize promise chaining with then(), we need to implement any logic you want to execute after the request in the promise chaining. Else like the example above, any code that you put after fetch() will execute immediately, before the fetch() is done.

Async/Await also makes it simple to convert code from synchronous procedure to asynchronous procedure.

Handling errors in an Async function

Error handling in async functions is very effortless. If the promise is rejected, we use the .catch() method to handle it.

Because async functions return a promise, we can invoke the function, and append the .catch() method to the end.

//handling errors in async functions
asyncFunctionCall().catch(err => {
  console.error(err)
});
Enter fullscreen mode Exit fullscreen mode

Similarly to synchronous code, if you want to handle the error directly inside the async function, we can use try/catch.

The try...catch statement is composed of a try block and either a catch block, a finally block, or both. try block code is executed first, and if it throws an exception, catch block code is executed.

See the code below:

async function someAsyncOps(){
 try {
   let response =  await fetch('https://jsonplaceholder.typicode.co') //api endpoint error
 let result = await response.json()
 console.log(result)

 } catch (error) {
 console.log("We got some error",error) //catches the error here
 }
}
someAsyncOps()
Enter fullscreen mode Exit fullscreen mode

The output of the code will be:

tryCatch

Summary

  • Using async keyword prior to a function automatically transforms it into a Promise.
  • When an await expression is used, the execution of an async function is paused until a promise settles (fulfilled or rejected), returns its result, and then the execution resumes.

  • Because the await is valid only inside async functions, the await expression does not block the main thread from executing.

  • With async/await we rarely need to write promise.then/catch
    -These features make writing both readable and writable asynchronous code a breeze.

Please do comment or add your feedback, insight, or suggestion to this post. It helps clarify any concerns you might have and makes the article better.

If you have found value in this article, kindly share it on your social media platforms, and don't forget to connect with me on Twitter

Top comments (0)

An Animated Guide to Node.js Event Lop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.