Asynchronous JavaScript Tutorial

In programming, we often hear the words synchronous and asynchronous. But what exactly is synchronous and asynchronous programming? Generally, these terms are used to differentiate the execution order of the tasks within a program. Before we jump further into the differences, let’s consider how a task is executed in JavaScript. JavaScript runtime has only one stack, that’s why it’s called as a single threaded language. Using one stack, it can only do one task at a time. So how can we make JavaScript do multiple tasks at the same time? To do so, we need to use a series of features that JavaScript offers.

The Call Stack

The call stack is a simple data structure that keeps track of program execution. The call stack uses the Last In First Out (LIFO) principle. This simple data structure can do only two things - adding an item to the top of the stack and removing the top item. A call stack is a data structure which records where we are in the program.

When a function is called, the function, its parameters, and its variables are pushed onto the stack and will be removed after the execution either finishes or implicitly returns a value. This is described as making the code execution in JavaScript synchronous.

The Heap

The Heap is an unstructured region of memory where objects are stored once the variables are defined.

Web APIs

What we called Web APIs here is the APIs that is provided by the browser. That are not part of JavaScript runtime, but are built on top of it. Browser Web APIs enable JavaScript to expose data from the browser and surrounding computer environment.

The Callback Queue

The Callback Queue contains a message queue to be processed once the call stack is empty. When a process in a Web API finishes, it is dropped to a callback queue. An Event loop will trigger the callback queue when the Call Stack is empty.

Let’s take the following example to see how these items can operate within the JavaScript runtime.

function multiply(a, b){

   return a*b;

}

function square(n){

   return multiply(n, n)

}

function printSquare(n){

   let sq = square(n)

   console.log(sq)

}

printSquare(5)

The code above will be executed in the following sequence.

Asynchronous in JavaScript sequence_1
Asynchronous in JavaScript sequence_2
Asynchronous in JavaScript sequence_3
Asynchronous in JavaScript sequence_4
Asynchronous in JavaScript sequence_5
Asynchronous in JavaScript sequence_6
Asynchronous in JavaScript sequence_7
Asynchronous in JavaScript sequence_8
Asynchronous in JavaScript sequence_9
Asynchronous in JavaScript sequence_10
  1. The function main() is called and pushed to the stack.

  2. The function print(5) is called and pushed to the stack.

  3. Inside the function print(), function square(5) is called and pushed to the stack.

  4. Inside the function square(), function multiply(5, 5) is called and pushed to the stack.

  5. Inside the function multiply(), a value is returned. As previously mentioned, a task on stack will be removed after it finishes or returns a value. In this case, the function multiply() will be removed from stack.

  6. The function square() also returns a value, so it will be removed as well.

  7. The function print() calls the Console.log function, so it will be pushed to the stack.

  8. The Console.log function prints the value and finishes the task, so it will be removed from the stack.

  9. The function print() has finished the task, so it will removed from the stack.

  10. Once all of the functions have finished, the function main() will be removed and the stack will become empty.

The example above shows that the process is run sequentially, with only the Call Stack playing a role on this example. This process is described as running synchronously. Synchronous programming is blocking and single threaded, using only one stack. In a synchronous program, it will be started at the first line of source code, with each line of code being executed sequentially thereafter. This means while one task is going on, other tasks won’t start before the running process is finished.

What if we want the process to become faster, without the need to wait for the running process? We can do it in using Asynchronous programming. Let’s use the following example to deep dive into the asynchronous concepts in JavaScript.

console.log("first?")

setTimeout(() => {

   console.log("second?")

}, 3000)

console.log("third?")

The code above will be executed as below.

asynchronous_sequence_1
asynchronous_sequence_2
asynchronous_sequence_3
asynchronous_sequence_4
asynchronous_sequence_5
asynchronous_sequence_6
asynchronous_sequence_7
asynchronous_sequence_8
asynchronous_sequence_9
asynchronous_sequence_10
asynchronous_sequence_11
asynchronous_sequence_12
asynchronous_sequence_13
  1. We run the code, and the function main() is called and pushed to the stack.

  2. The console.log function is called and pushed to the stack.

  3. The console.log function prints the value “first?”, the task is finished, and it is removed from the stack.

  4. The setTimeout function is called and pushed to the stack. When we call a setTimeout function, we pass a callback function and a delay to set the timer. This function is an API provided by the browser.

  5. The timer is set, and runs on the web API with the setTimeout callback. At this point the setTimeout function is removed from the stack, so it can continue to the next stack.

  6. The next console.log function is called then pushed to the stack.

  7. The console.log function prints the value “third?”, finishes the task, and is removed from the stack.

  8. All the functions have finished, so the function main() is removed and the stack becomes empty.

  9. While the task is processed on the stack, the timer keeps counting on the web API. When it done, it pushes the callback function to the callback queue.

  10. In this part of the process, we will need the Event loop. The Event loop has one simple job - to look at the stack and check if it is empty or not. If stack is empty, it will take the first thing on the queue and push it to the stack. As the stack is currently empty, the callback function will be pushed to the stack.

  11. As the callback function is currently on the stack, the console.log function inside it will be called and pushed to the stack.

  12. The console.log function prints the value “second?”, finishes the task, and is removed from the stack.

  13. Once the callback function has finished, it is removed from the stack, leaving it empty.

So, we have run the code asynchronously. It doesn’t need to wait for the running process to finish before continuing the next process, as the asynchronous programming is non-blocking. This makes the process run much faster. The examples above show how synchronous and asynchronous programming can work. So, how can we write asynchronous code in JavaScript? We need to discuss the concepts of Promise and Async/await.

PROMISE

A promise is an object that will return a value after it completes. The value can be success or failure. That will return value A promise may be in one of these possible states.

  1. Fulfilled: a state or condition when a promise is already finished and returned a resolved value.

  2. Rejected: a state or condition when a promise has not resolved (which may occur in the case of a network error).

  3. Pending: a state or condition when a promise is ongoing and hasn’t return any value, and is neither resolved nor rejected.

Let’s have a look at how to write a function using promise. Here we have a function called doubledAfter2Seconds, this function will return a promise.

function doubledAfter2Seconds(num){

   return new Promise((resolve, reject) => {

       setTimeout(() => {

           resolve(2*num)

       }, 2000);

   });

}

This function will be called as the following code using the promise method.

function addByPromise(){

   doubledAfter2Seconds(10).then((res) => {

       return doubledAfter2Seconds(res)

   }).then((res) => {

       return doubledAfter2Seconds(res)

   }).then((sum) => {

       return sum

   }).catch((error) => {

       return error

   })

}

The function .then is a function that runs when the promise is resolved and receives the result. The function .catch is a function that runs when the promise is rejected and receives the error.

ASYNC/AWAIT

Async/await is a part of ES2017, and has offers a new way to write asynchronous code. It lets us define a function as asynchronous by adding the async keyword before the function name. We can then call this function and wait for the promise to be fulfilled or rejected. Await must be used inside the async function. An async function is a shortcut for defining a function which returns a promise.

// function that return a promise

function func(){

   return Promise.resolve("hello")

}

 

// async function that equivalent to func

async function asyncFunc(){

   return "hello"

}

Let’s compare how will the code looks like using async/await. We will use the doubledAfter2Seconds function above and call it using async/await.

async function addByAsync(){

   try{

       const a = await doubledAfter2Seconds(10)

       const b = await doubledAfter2Seconds(a)

       const c = await doubledAfter2Seconds(b)

       return c

   }catch(error){

       return error

   }

}

Notice the difference? The code looks cleaner when we write it using async/await. Await allows us to synchronously wait on a promise. Using await you can write asynchronous code that looks like synchronous code.


CONCLUSION

It’s clear now that with only one stack, JavaScript can only do a synchronize programming. But using promise or async/await function and the helps of browser Web APIs, it is able to execute the code asynchronously. Async/await as the new feature that has been added to JavaScript provides a way to enable us write asynchronous code looks like synchronous code, now the code looks more obvious