Hi Reader, nice to meet you! In this blog I will teach you about asynchronous JavaScript. It seems like such an important topic if you are studying JavaScript and It really is important !! . . . Of course you can go through docs or GeeksForGeeks but here I will try to make it very simple and clear to you with many examples. So, get on the ride ! ^ ^
Index
Single Threaded Nature of JavaScript
Role of web browsers
Callbacks
Callback Hell
Promises in JavaScript
Async/ Await in JavaScript
If JavaScript is single threaded then how asynchronous?
Now, before moving onto this question ( a pretty trick ques sometimes asked in interviews ) lets first understand what is this thing ! " single threaded nature " . No, its not related to actual thread :) 🧵( I wish it was ) ... Anyways -
It simply means that JavaScript has a one track mind. Like it can't do multitask. JavaScript executes code line by line. It will finish Task1, then move to task2 and then to task3..While other While other programming languages 📔allow for multiple threads to run simultaneously, each executing its own sequence of instructions,🛠️ JavaScript does not offer this capability.
Just like, some person X cant cook while reading a book. Maybe he will first cook and then read or vice versa but can't do both together. Similarly, Js can't execute one function at the same time executing another function. This is single threaded nature. It's synchronous, but at times that can be harmful. For example, if a function takes awhile to execute or has to wait on something, it freezes everything up in the meanwhile.
If you want to know more about why Js don't have multithreading like other languages
Asynchronous
Asynchronous means its not synchronous (don't hate me for that line haha). But asynchronous in JS means codes executing parallelly, or not in any proper order.
I know I know, we just learnt that JS is single threaded then why there is a thing called Async Js and how is it possible! We have a workaround...
Role of Web browser
Lets take that example again, X is cooking and wants to read side by side. X can call his little brother and ask him to read out loud for him. At the same time, X cooks. Now, X is able to read while cooking. X is smart. Be like X, jk.
In layman language, there are certain functions recognized by JavaScript Engine that when it sees them, it hands off them to web browser to execute it in the background. For example,
- setTimeout Function() - delay the code for given seconds or milliseconds and then runs the code which is inside the function
Here's a scenario -
console.log("I print First"); //First
setTimeout( ()=>{
console.log("I print after 3 Seconds");
}, 3000);
//Third
console.log("I print second"); //second
When the compiler gets to the setTimeout function, it has already printed the first console.log
Then, it hands the setTimeout func to web browser to run in background. The web browser counts for 1....2....3 in the background
and in the meanwhile, the compiler prints the second console.log (web browser is still counting in the background)
By this time the 3 seconds are up and the web browser reminds the compiler "Hey, times up!".
Compiler prints that 3rd console.log
Output -
So, this is how Js is working asynchronous even though it is single threaded🎈
Let's move onto next topic CALLBACKS ?? 📞
Callback Functions
"I will call back later!"
Thats where it got its name from
Callback functions are functions which are passed as arguments in other function. In this way, a function calls another function ( yeah similar to recursion ) But in recursion, the function calls itself.
In this case, the main function calls the "passed as argument function" / calls back some other functions. Don't get confused, we will look into examples :)
function myDisplayer(some){
document.getElementById('demo').innerHTML = some;
};
function myMessage(fName, lName, myCallBack){
//This is a function which has 3 arguments: firstname, lastname
//and a callback function
//Notice, I didn't declare that function as 'myCallBack()'
let message = `Hello, ${fName} ${lName}. How are you ?`;
myCallBack(message); //callback
};
myMessage('Tony', 'Chopper', myDisplayer); //myDisplayer function is passed
//as a callback function
In the above example, you will notice that myDisplayer function is passed as a callback function in myMessage function. Heck you can even create an anonymous function like -
myMessage('Tony', 'Chopper', (some)=>{
alert(some);
document.getElementById('demo').innerHTML = some;
}
Now, this was a simple example just to explain the syntax of callbacks but why do we study them ? A callback function can run after another function has finished. Where callbacks really shine are in asynchronous functions, where one function has to wait for another function (like waiting for a file to load).
But, sometimes using callbacks is a bad idea.
Callback hell
After the dramatic introduction lets see what is a callback hell.
It is nested callbacks making a pyramid like structure which is not something you will want to read. It looks so messy and difficult to read. Every callback is dependent/ waits for the previous callback. This code is also hard to maintain.
Example -
const delayedMessage(firstName, lastName, myCallback){
setTimeout(()=>{
let message = `Hi, ${firstName} ${lastName}. Welcome!`;
document.getElementById('container').innerHTML = message;
myCallback(); //it will be called after 2 seconds
}, 2000)
}
Above is a function which prints the message after 2 seconds. It takes three arguments and one of them is a callback function. Still no problem until now...
delayedMessage('Sagar', 'Sharma', ()=>{
//this callback function being passed calls delayedMessage again
delayedMessage('Rome', 'Beck', ()=>{
delayedMessage('Riya', 'Sims', ()=>{
delayedMessage('John', 'Ben', ()=>{
delayedMessage('Charles' , 'Afflick');
});
});
});
});
This pyramid structure is so hideous to read and maintain. Let's take another somewhat real world looking example -
searchMoviesAPI('amadeus', () => {
saveToMyDB(movies, () => {
//if it works, run this:
}, () => {
//if it doesn't work, run this:
})
}, () => {
//if API is down, or request failed
})
Another Example-
const fs = require('fs');
fs.readFile('file1.txt', 'utf8', (err, data1) => {
if (err) {
console.error('Error reading file1.txt');
} else {
fs.readFile('file2.txt', 'utf8', (err, data2) => {
if (err) {
console.error('Error reading file2.txt');
} else {
fs.writeFile('output.txt', data1 + data2, 'utf8', (err) => {
if (err) {
console.error('Error writing to output.txt');
} else {
console.log('Files successfully read and merged into output.txt');
}
});
}
});
}
}); // I know this code is hard to understand..thats the point
All these nested Callbacks create the "callback hell"
Promises in JavaScript
To tackle this problem, promises were introduced in JavaScript. These are recent additions and promise for a better future I guess, ... that's why the name. Anyways -
A promise is an object representing the eventual completion or failure of an Asynchronous operation.
Let's say you are getting data from some API... It might work or it might not. In the previous method using callbacks you have to pass two callbacks one for failure and one for success. If its success, we have to fetch next set of data and pass another two callbacks ( one for success and one for failure ). and on and on ... ahh.. callback hell.
promises syntax
const someFunction = (url) =>{ //its some function which takes url
//from user and return a promise object
//we declare a promise object using 'Promise" keyword
return new Promise((resolve, reject) => {
//some api called..it will either work or won't
}) // resolve and reject are two arguments passed. These are user-
// defined names..but in programming world these words are commonly
// used
};
We have created a promise object above.. although usually, we don't have to create promises rather we just have to work with created ones...using .then() and .catch() methods. You just need to focus on understanding how these two work
someFunction()
.then(data){
console.log("Promise is resolved");
console.log("here is your data!", data);
}
.catch(err){
console.log("Error 404", err);
}
If the promise is resolved, then() method is called. If error is here , catch() method is called. Right now, its similar to callback hell if there is nested structure like if API is success, fetch next set of data..[ .then and .catch for that too ]
But, we can chain these .then() methods to make the code more readable and finally escaping callback hell !
// RETURN A PROMISE FROM .THEN() CALLBACK SO WE CAN CHAIN!
fakeRequestPromise('yelp.com/api/coffee/page1')
.then((data) => {
console.log("IT WORKED!!!!!! (page1)")
console.log(data)
return fakeRequestPromise('yelp.com/api/coffee/page2')
})
.then((data) => {
console.log("IT WORKED!!!!!! (page2)")
console.log(data)
return fakeRequestPromise('yelp.com/api/coffee/page3')
})
.then((data) => {
console.log("IT WORKED!!!!!! (page3)")
console.log(data)
})
.catch((err) => {
console.log("OH NO, A REQUEST FAILED!!!")
console.log(err)
}) //lot more readable and easy to understand
Async / Await in JavaScript
Finally, the last section of this looong blog.. I hope you didn't get bored reading till here :) Congrats reader if you have reached this section.
Async and Await are newer and cleaner syntax for working with async code! Syntax "make up" for promises. We are kinda just beautifying promises.
Async
Await
Async Keyword
const message = async () =>{
//some code here
return 'hello user';
}
When we declare a function as async, now this function automatically returns a promise. We don't even need to create a new promise object. We can still use .then() and .catch() methods
message()
.then((data) => {
console.log("PROMISE RESOLVED WITH:", data)
}) //output will be "PROMISE RESOLVED WITH: hello user"
.catch(()=> {
console.log("OH NO, PROMISE REJECTED!")
})
if returning a value -> promise resolved
if there is an error -> rejected
//Example 2
const login = async (username, password) => {
if (!username || !password) throw 'Missing Credentials'
if (password === 'jackofalltrades') return 'WELCOME!'
throw 'Invalid Password'
}
login('jack', 'jackofalltrades')
.then(msg => {
console.log("LOGGED IN!")
console.log(msg)
})
.catch(err => {
console.log("ERROR!")
console.log(err)
})
Await keyword
This keyword can only be declared inside an async function. It waits for the promise to be resolved. If it is resolved then, that data can be stored inside a variable for future use.
For example, instead of using .then() and .catch() methods for the above code,
async function main(){
let data1 = await login('Jack', 'jackofalltrades');
let data2 = await login('Pisces' 'masterofnone');
console.log("data 1 is: ", data1);
console.log("data 2 is: ", data2);
}
'await' waits for the promise to be resolved and only if it is resolved we can store that value somewhere. NO CALLBACKS, NO HELL, NO .THEN() AND .CATCH(), NO PROMISES. So Simple.
Suppose you are fetching some data from API, you can simply use await keyword before inside an Async function and you are sorted !!
But, what about errors?
Yes, we don't use .catch() or any callback to handle errors. So , how can we handle errors now ?? What if our API doesn't work or something happens ??
async function makeTwoRequests() {
try {
let data1 = await fakeRequest('/page1');
console.log(data1);
let data2 = await fakeRequest('/page2');
console.log(data2);
} catch (e) {
console.log("CAUGHT AN ERROR!")
console.log("error is:", e)
}
} //Good ol.. try catch will do the job!
Asynchronous JavaScript concepts are necessary to learn if you are going to interact with APIs and work with unpredictable real world requests and files access in JavaScript.
Let's end our blog here. I hope I was able to teach you all about basic Asynchronous JavaScript. If you have any doubts or remarks, please share in the comments 😊