JavaScript Fundamentals: Async/Await

Make your code cleaner, more readable with the best feature in ES8

Async-await may be one of the greatest recent additions to JavaScript. It not only makes a programmer’s job easier, it also makes the code look clean and smooth.

JavaScript is a very versatile language which can perform functional programming, object-oriented programming, server-side as well as client-side optimizations, and a lot more.

Often times, in our day to day JavaScript code, we may need to perform a lot of asynchronous tasks behind the scenes. Implementation of asynchronous tasks gets complicated as JavaScript is a synchronous language.

In this piece, we are going to learn about the two new functions which came with the release of ES8 i.e async-await.

With the help of async-await, we can now write perfectly synchronous-looking code while letting them handle the asynchronous tasks in the back.

JavaScript as a Synchronous Language

What is an event loop and how does it work?

JavaScript is synchronous and single-threaded language. Asynchronous behavior is not directly a part of JavaScript. Rather, it can be built on top of the core JavaScript language itself, and it can be accessed through browser APIs.

What is an event loop and how does it work?

To understand the concept behind an event loop, we first need to look at how a call stack works. A call stack is like a log of the current execution of the program.

We use call stack because it sometimes gets difficult even for the JavaScript engine to keep track of all the program execution; it relies on call stack to provide valuable information.

The call stack is very similar to a normal stack with the same operations, namely push and pop. A stack data structure follows the concept of LIFO(Last in First Out), and by push, we mean putting something into the stack.

When a function executes/calls itself first, the JavaScript engine pushes that function into the call stack. Every function call pushes itself into the call stack sequentially.

Similarly, pop means to remove something from the stack. When the function at the very top of the stack finishes execution, it gets removed from the call stack as it follows the LIFO property.

How call stacks work

Now that we know what properties and concepts a call stack follows, let’s look at how it works practically. Have a look at this code snippet:

In this code, we have three console.log() statements inside the main() function, which simply logs the string value in the terminal.

Two of these statements are presented right at the top and bottom, at lines two and six respectively. The other statement is between the two, wrapping itself inside a setTimeout call (using a browser API, which we’ll talk about later) with a zero millisecond wait time.

How the event loop works

  • As the program starts, it enters into the main function. As a result, main() pushes itself into the call stack first as a frame. After entering the main function, the first statement that executes is console.log(“First”). This statement also pushes itself into the stack by the browser. Upon execution, it pops the frame out and displays “First” in the console.
  • Upon execution of the first statement, the next statement pushes itself to the stack(setTimeout). This statement uses a browser API to delay the execution time of the callback function present inside. This frame pops itself out of the stack after handing over the middle() function to the browser for execution. This is because it is a browser API, and it executes at a certain time of zero milliseconds.
  • After this executes, the third statement, or console.log(“Third”), pushes itself to the call stack for execution. This happens while the timer runs in the background to execute the middle() function. Once the browser pushes this frame into the call stack, it executes and displays “Third” in the console, popping out the final frame.
  • The setTimeout function gets a zero millisecond delay, causing the callback to add to the event queue as soon as the browser receives it, since the execution time has already expired.
  • As soon as the last statement executes and pops out of the call stack, the main() frame pops out too, thus making it empty. For the browser to push any message from the event queue to the call stack, it has to be completely empty. This is why, even though the delay was zero milliseconds, the callback still has to wait.
  • Finally, the statement console.log(“Second”) inside the callback middle() pushes itself into the call stack and executes, thus printing “Second” into the console.

This is the event loop of JavaScript:

What Are Promises?

How promises work

Promises in JavaScript are exactly like promises in real life.

Imagine that you have promised a friend to buy him his favorite book. To fulfill this promise, you need to:

  • Go into any book store where you’d possibly find the book.
  • Buy it and bring it back to him

While this is happening, all that your friend is doing is waiting for you.

Imagine if the promise was something else, like getting into your favorite company or buying a very expensive car. These promises will take a much longer time to fulfill, thus a greater waiting time.

Similarly, in JavaScript a promise is an object which gives out a value sometime in the future. It may return a successful output or give us the error result, but it won’t stop the execution of the program midway.

Promises contain three states:

  • Fulfilled
  • Rejected
  • Pending

How promises work

A promise is an object which is able to return itself synchronously from an asynchronous function.

If you are requesting something from a program(promise), it stays in the pending state until the time it is over/complete.

Once the conditions are fulfilled, then the promise is resolved, resolve(), or rejected, reject(). Once a promise settles, it cannot resettle.

Take a look at this code snippet:

In this code, we pass on a promise which takes two parameters, resolve and reject. It can have any name, but this examples helps us to better understand.

In this example, let’s say the promise is resolved — the work it is supposed to do is complete — and we are passing it on using the resolve() method.

Since the function is already resolved, it won’t throw any error. Instead, it will execute the success function, printing the message in the first .then() method.

Take a look at this code snippet:

In this code snippet, we do the same thing, but let’s say the request/result we wanted to get from this promise was rejected/failed.

In that case, we are passing on a reject() method, which the .then() method handles again. It automatically invokes the error function(since it comes under reject() and is an error), which in turn displays the error message.

Important rules of promise

A promise’s standard has already been defined by the Promises/A+ specification community. Typically, promises always follows a specific set of rules:

  • A promise object should always supply a .then() method.
  • A promise which is pending will always transition back to either resolve or reject.
  • A promise, whether resolved or rejected, is always settled and must not transition into any other state.
  • Once a promise settles, it has some value. That value must not change.

What Is Async-Await?

Async

Until now, we have been using promises to deal with asynchrony in our code. That works fine, but the new release of async-await in ES8 has been even more helpful in allowing us to write super smooth, synchronous-looking code.

According to MDN(Mozilla Developer Network):

“An asynchronous function is a function which operates asynchronously via the event loop, using an implicit promise to return its result. But the syntax and structure of your code using async functions is much more like using standard synchronous functions.”

Now, let’s unpack the meaning of the above sentence.

  • Async-await is very easy to use and, just like any other JavaScript function, it also operates asynchronously via the event loop which we discussed earlier.
  • It is implicitly related to promises and uses it to return the result.
  • The syntax structure is the same as writing a synchronous function.

Async

Async-await is just an extension of promises. It enables us to write promise-based code synchronously without blocking the execution thread. An async function implies that a promise value will return.

It works asynchronously via the event loop. An async function always returns a value. JavaScript automatically returns a resolved value with it if doesn’t get a promise back as a return.

To understand this, let’s look at this code:

Running the above code will give us an alert output with the string message Hello World.

This means a resolved promise successfully returns by default. If it was the other way around, the .then() method wouldn’t execute.

Await

The await keyword handles the waiting part of the async-await block. This operator is used to wait for a promise.

It is functional inside the async block and waits for the promise to return a result. Await only makes the async function block wait, not the whole program.

Let’s understand this by looking at this code:

When we call the display() function at the end, it invokes the AwaitExample() function enclosed in an await block.

What this means is that until the time the promise resolves itself on the AwaitExample() function, the await keyword will hold the execution of thedisplay() method.

It won’t stop the program from running, but will only hold the display() method. After two seconds, or 2000 milliseconds, the promise resolves, which the console then displays.

Rules to follow while using async-await

We should take care of a few rules before we use async-await in our code block. These rules are necessary to make your function work properly.

Let’s take a quick look:

  • We can’t use the await keyword in a normal function. You must have an async function to be able to use await inside of it.
  • Async-await makes execution always sequential. Having a parallel execution is must faster.

Async-await is very powerful, but it comes with caveats. If we use them properly, they help to make our code very readable and efficient.

Sequential vs. Parallel Execution

Sequential execution

As the name suggests, sequential execution means executing your code in a sequence. This process is a little slower if we compare it to parallel execution, since in sequential execution one line has to finish processing before it can execute the next line.

Let’s understand this with the help of an example. Take a look at this code snippet:

In this example, we are just returning two promise functions. But when the first one calls itself, it takes two seconds to execute. Meanwhile, the other await function is just sitting there.

After the first promise resolves, only then does the next await start execution. This process is time-consuming when we compare it to parallel execution.

Parallel execution

A much more efficient way of handling faster execution is through parallel execution.

Basically, it won’t stop the execution of other await methods if there is more than one present.

It parallel processes all the await methods. This type of processing uses the promise.all() method. Let’s understand this with the help of this example:

In this code snippet, we use a method called promise.all(). The function of this method is to resolve all the promises inside of the iterable first, and then returns the final result.

Thus, since the individual functions run in parallel, execution is much faster when we compare it to sequential execution.

Error Handling in Async-Await

Another very nice feature about asynchronous function is that async-await can handle errors synchronously, as well as with the help of try and catch block.

Depending upon whether the value gets resolved or rejected, our try and catch block will display the required output, and the process flow will be synchronous as well.

Let’s take a look at an example:

In this example, we observe that the Promise gets either resolved or rejected depending on the random value generated by the val variable.

If the value is one, then it resolves, later handling itself in the try block. If it’s the other way around, then the JavaScript rejects the promise and it handles itself in the catch block, thus giving us a smooth execution flow throughout.

That brings us to the end of this piece. I hope you enjoyed learning about what impact async-await has in the JavaScript world right now.

Thank you for reading. Peace out. ✌️