Asynchronous JavaScript

Asynchronous JavaScript

Β·

15 min read

Maybe you have heard the Word Javascript is a synchronous single-threaded language as it has only a single call stack and the execution of commands happens one after the other. though it is called synchronous single-threaded, it also does execute the code asynchronously.

understanding Asynchronous in Js:

let us see the below code working :

console.log('Iam First');
console.log('Iam Second');
//output: 
//Iam First
//Iam Second

As the expected output is printing one after the other.because we know js is synchronous right...

But there is a case that, commands will take time to do a task for example loading an image, fetching an API, or loading the scripts... these cause the blocking of further commands in the script and even the page becomes unresponsive for a certain amount of time until the task's like mentioned are executed synchronously. so these tasks will be handled asynchronously.

whenever javascript engine sees the asynchronous code, that task will be thrown out of the call stack and assigned to the web API environment. if the web environment's part of fetching the URL/ loading the image/ waiting for a certain time(setTimeout) /... is done, then the callbacks are pushed into the task queue. once the call stack is empty the entire tasks queue tasks are executed in call stack.

and Here event loop plays a role in checking whether the call stack is empty or not, if it is empty then all the tasks placed in the queue are executed in call stack one after the other on first come first serve basis.

Hm..., okay .let's check by inserting the setTimeout function, setTimeOut function is an asynchronous function. so it helps us in understanding the asynchronous functionality better. basically setTimeout will take the function, delay, and other arguments (arguments are used as parameters in the function ).
setTimeOut(fn(){},delay,[arg1,arg2,...)

fn(){} the function passed in the setTimeOutfunction call is called callback function since it is executed after some delay .

setTimeout(function iamCallBackFunction(){
    console.log("i will be mostly executed after 1 second");
},1000);
console.log('Iam First');
console.log("Iam Second");
//output:
//Iam First
//Iam Second
//i will be mostly executed after 1 second

so here, when the js engine sees the setTimeout function, its callback function will be moved to the web API environment to wait for a certain delay of time mentioned in the second argument 1000milli seconds. That can be assumed by the below image.

once the call back waits for 1 second in the web API environment, it is moved to the task Queue/macrotask (V8 terminology) queue. eventually, in the meantime, the call stack continues to execute other code.

Now if we see the below figures first console.log function execution is done output is logged and started executing the second console.log function and its output also got logged, here console.log functions are not asynchronous functions so we don't see them moved to the task queue though the console object is a web API object.

Now we see everything from the call stack is executed, if there is no other code execution is there to do, then GEC will also be removed by the JS Engine from the call stack.

But Wait, the call stack is free but other tasks are pending in the macro task queue, so the event loop recognizes this and moves the task from the macro task queue to the call stack. (task here referring to setTimeOut callback function ).
setTimeOut callback function named iamcallBackFunction executed in a call stack and logged a message.

Now everything is doneπŸŽ‰. we can see all outputs logged accordingly.

Hope, until now it is clear how the asynchronous task works πŸ™‚. But this is a simple example of asynchronous js which waits for a certain amount of time we specify.
let's imagine we are making an API/Server call like this which is also doing an asynchronous task because script loading can be handled by web API environments asynchronously.

var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerHTML =
      this.responseText;
      //  if we need to do further server calls based on the previous server                                                call response inside the  
     // onreadystatechange ....
    }
  };
  xhttp.open("GET", "ajax_info.txt", true);
  xhttp.send();

okay... why do API calls using xmlHttpRequest though we have other methods like fetch..., actually fetch method is an asynchronous function which returns a promise. earlier days of ajax request is done with xmlHttpRequest no promises were introduced, we will continue to understand why promises were introduced.

if you see the above code. we are using event listeners, once the API call is done, onreadystatechange event is fired on the readyState property change.

suppose we want to do another API that depends on the response sent by the previous API call, I have to do it inside the event listener callback iamCallBack function only. that may look like below code.

var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function iamCallBack() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerHTML =
      this.responseText;
      var xhttp1=new XMLHttpRequest();
       xhttp1.onreadystatechange = function() {
         if (this.readyState == 4 && this.status == 200) {
            console.log(this.responseText);
            // another  api call if you want..
         }
       }
      xhttp1.open('GET',"ajax_info_1.txt",true);
      xhttp1.send();
    }
  };
  xhttp.open("GET", "ajax_info.txt", true);
  xhttp.send();

and assume, that I want to do another API call inside nested API. so on...,

so sometimes the depth of the callbacks increases and which is not readable and may cause confusion. this way of forming a callback is called callback hell or pyramid of doom.

The pyramid of doom : r/ProgrammerHumor

Also, it is not a bad idea if we create functions and place all the other nested API calls in that function, instead of these nested callbacks. Like below.

var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function iamCallBack() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("demo").innerHTML =
      this.responseText;
     APICallOne();
    }
  };
  xhttp.open("GET", "ajax_info.txt", true);
  xhttp.send();

function APICallOne(){
 var xhttp1=new XMLHttpRequest();
    xhttp1.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      console.log(this.responseText);
      // another  api call if you want..
    }
  }
 xhttp1.open('GET',"ajax_info_1.txt",true);
 xhttp1.send();
}

But here the programmer has to look up the all other functions throughout the script. which may sometimes confuse in case of complex code, so we want to do something that the flow of code is easily understandable without making a call back hell πŸ€”.
That is done by the promises πŸ’‘.

Promises:

yes, callback hell can be avoided by chaining promises. before understanding it let us go deep init.
okay.. but what are the promises ..?,

Promise is the object used as a placeholder for the future result of an asynchronous operation. or we can say it is, a container for an asynchronously delivered value.

promises can be created with the new Promises()constructor, the constructor function takes the executor function which is executed/called immediately with new Promise constructor. the executor function takes the first parameter as resolve function and a second parameter as the reject function.

The name of the parameters can be anything not permitted to resolve and reject only, we can name it as per the wish, but the order matters.

These are the callback functions passed inside the executor by the JS engine itself we have to call one of it inside this executor function.

if we observe the output of the above promise code, it is returning an object with 2 properties promiseState and promiseResult.

promises will have to undergo pending, fulfilled/rejected.

from the above code example, if u try to immediately console the promise object, you will see the status as pending since the timer of 4 seconds has not been done yet. and the initial value will be undefined. because the promise is not settled yet, at that time of execution.

promise must return either resolved/fulfilled or rejected promise or we can simply say a promise must be settled eventually after the pending state. then those property state will be changed to fulfilled/rejected. and value will be assigned with whatever value that is passed through the resolve /reject callbacks like this resolve('resolved') and reject('rejected').

Now let us convert the xmlHttpRequest code example that we took while understanding callback hell into a promise.

const promise=new Promise((resolve,reject)=>{
    var xhttp = new XMLHttpRequest();
      xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          resolve("i  resolved");
        }
        else{
          reject("something went wrong");
        }
      };
      xhttp.open("GET", "ajax_info.txt", true);
      xhttp.send();
  }).then(response=>{console.log(response)})
  .catch(err=>console.error(err+"πŸ“Œ"));

once the API call is done, onreadystatechange event is fired and inside event callback function, we are calling the callbacks of resolve and reject and passing certain values into resolve and reject functions.
like here resolve("i resolved") and reject("something went wrong") . based on the response we got from the server, these functions are called and return promise object with the change of state and value property as we discussed. and we can see below as the above code returned a rejected promise because of an unsuccessful server call. consoles the caught error.

consuming Promises:

As we saw from previous code example, promises are handled/consumed with then method and catch method and finally method, followed by the dot . operator.

.then/.catch/.finally methods are executed asynchronously and these are enqueued inside a micro task queue. now, what is this microtask queue? πŸ˜‘.., we will cover it in detail 😁 after this consuming promises.

.then :

  • then method callback function gets executed on fulfillment of the promise, i.e if the resolve method is called within the executor function. then function takes the response as the then's callback parameter. then(f(){}) .

  • then method can take 2 arguments first parameter for the result, and second parameter for catching the error, if any passed through the previous promises.

  • then returns a promise, so that then can also be chained with .then on it. if then callback function returns 2, then 2 will become the result of that promise and it handled but other then method chained to it and so on.... like this.

    Promise().then().then().then().catch() .

  • if any error occurs in the then method can be handled in the following then method's second parameter if specified, else handled by catch method.

.catch :

  • catch method callback function gets executed on rejection/failure of the promise, i.e if the reject method is called within the executor function, it receives an error in the catch's callback parameter. catch(f(error){})

  • catch handles the errors thrown from previous then methods also, catch can also throw error inside catch callback method and the other catch method which is followed by it handles it.

so errors are handled similarly like try and catch blocks. both then and catch handles errors which are thrown by the reject() method and or using the error object throw new Error('').

.finally :

  • we can chain finally method to the promise also, to do some task once promise is settled. finally(f(){})

  • A finally handler doesn’t get the outcome of the previous handler (it has no arguments). This outcome is passed through instead, to the next suitable handler.

  • If a finally handler returns something, it’s ignored. When finally throws an error, then the execution goes to the nearest error handler.

so until now, we discussed on :

  • what are the asynchronous functions & why we need it.

  • Execution of asynchronous tasks inside the call stack with the setTimeOut function.

  • how the callback hell is formed and introduction to promises and the way of consuming the promises.

Micro task Queue :

  • microtask queue(V8 terminology) is similar to macrotask queue, whatever tasks are enqueued are executed once the currently executing call stack becomes free. but there are some differences comes in the priority of execution.

  • whenever call stack becomes empty/free, event loop gives the priority to microtask queue and it will not give chance to macrotask queue until all tasks in the microtask queue executed. and again once the call stack is free macrotask queue is executed.

  • whenever code is encountered with promise handlers .then/.catch/.finally these are pushed into the microtask queue.

  • And even if in the middle of the macrotask, if task hits the promise code, then, after completing the currently executing macrotask task, promises callbacks/tasks present in the microtask queue moved to the call stack and executed, and after all tasks in micro task are executed, then execution of macro task queue tasks continues. below mentions a line from javscript.info site.

Immediately after every macrotask, the engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else.

we will take the below example for understanding the executionof microtask's :

setTimeout(()=>console.log("setTimeout 1s"),1000);
let promise = new Promise(function(resolve, reject) {
    setTimeout(()=>console.log("setTimeOut 0.6s"),600);
    setTimeout(() => resolve("promise inside setTimeout 0.5s"), 500);
 }).then((res)=>{ console.log(res)});
console.log("First");

//output:
// First
// promise inside setTimeout 0.5s
// setTimeOut 0.6s
// setTimeout 1s

Let us understand it by call stack queue execution visuals:

  • so GEC(Global Execution Context) is created as script is loaded. as soon as the call stack hits the setTimeout function its call back is moved into web API to wait for delay of 1 second 1000 ms. after delegating work to webAPI setTimeoutwork is done, so moved out of call stack.

  • Then call stack hits the promise code and starts executing the promise function and it hits a setTimeout function, callback function of setTimeout is also moved to web API, where a timer of 0.6 seconds starts.

  • then again inside the promise it finds the setTimeout function at line 4. with a delay 0.5 seconds, then its callback also moved to the web API environment to handle it for 0.5 seconds.

  • setTimeout and promise function execution is done, so these are moved out of the call stack.

  • code execution continues and it encounters a console.log function and executes it and logs the output. meanwhile, if the timer inside the web API completes, callbacks are moved inside the macro task queue, here we are considering the timer of 0.5 seconds was enqueued into the queue first.

  • if we observe from the code, the 3rd and 4th line setTimeout functions may have execution differences of minor milliseconds, due to its setTimeout delay difference of 0.1 second. 4th line setTimeout callback is moved into the task queue and executed first if the call stack is empty.

  • As we discussed callbacks are queued up in macrotask queue. when call stack is empty, callback is moved into the call stack for execution.

  • callback function having the resolved promise*,* then the then function is called, passes its callback to the microtask queue and the currently executing callback is moved out of the call stack.see below figure.

  • from the below diagram representation, from FIG-1 we can see that, event loop finds there is task pending in microtask queue, though there are callbacks to be executed from macrotask. priority of execution is given to microtask queue. and only to be executed if there is no task in mid-execution.
    from FIG-2 execution is happening and in FIG-3 output is logged in the console. then continues to execute the remaining task from macro task queue since there are no microtask left. and logs all the outputs.

so this is the way promises are executed in microtask.πŸ₯³.

you know there is another easy way of implementing promise, that is done by async and await. yes, let us dive into it.

Async/Await:

Async/await is introduced in ES7 to make promises implement easily. as everybody says, we can say these are syntactic sugar of promise.

Have a look at the below code, To get us familiarized with async await syntax.

const asyncFunction = async function(){
    let response = await Promise.resolve(1);
    return response;
}

to make a function work asynchronously, we need to add async before the function declaration /expression. async function returns promises.

const asyncFunction = async function(){
    return 1;
}
console.log(asyncFunction()); // promise object

so that means we can handle this returned promise with promise handlers like then and catch. yes absolutely.

const asyncFunction = async function(){
  let response = await Promise.reject(1);
  return response;
}
asyncFunction()
.then(res=>console.log(res,"inside then"))
.catch(err=>console.log(err,"inside catch"));

but, here we are not using the await usage effectively, to avoid this chain of callbacks we must use await. and we can handle the promises internally inside the async functions only.

await is followed with a promise or expression like await promise/expression. if any value is passed instead of a promise, then it will be first converted to a promise, and then after the settlement of the promise, the value of the promise is returned as a result of the await expression. and async function can have 0 or more await expressions.

1.const asyncFunction = async function(){
2.  let response = await 1;
3.  return response;
4. }

we can see, line 2 let response = await 1; "1" will be converted to promise, and its promise settled value will be returned to response .line 2 can be implemented in a promise like this let response = await Promise.resolve(1);. but if we want to return any static value there is no need of implementing promises, this is just for a case of analysis.

when the engine hits await expression, async function execution is stopped. and async function is moved from the currently executing call stack. call stack continues to execute the remaining commands present in the script. once the promise settles(fulfilled/rejected), async function is moved into the microtask queue.it is like adding the rest of the code after await into then function.

once event loop tick checks that the call stack is empty and microtasks are there to do, async function task is moved to call stack and execution is resumed from line 3.

use of async and await enables the use of try and catch blocks, for example like below. and if any errors are uncaught inside async function those can be handled by then and catch method as seen in above examples.

const asyncFunction = async function(){
  try{
   let response = await Promise.reject(1);
   console.log(response);
   return response;
  }
  catch(error){
    console.error('here we got error',error)
  }
}
console.log("ok")
asyncFunction();

output:

That's it ...,πŸŽ‰πŸ₯³. we are done.

Conclusion:

code blocking in the call stack can be avoided by executing the javascript code asynchronously. callbacks play a major role in handling the js asynchronously, while dealing with callbacks we may fall into a callback hell, so to avoid callback hell and also to prioritize the task promises were introduced which are executed in the microtask queue which is given more priority than macro task. Promises are made easy with async and await.

References:

Β