Async JavaScript. Ten Minutes of Your Interview Covered.

Akhilesh Ojha
The Startup
Published in
9 min readSep 20, 2020

Welcome to the blog where I answer the most asked JavaScript Interview question, after Life Cycles of course :P.

What are callbacks, promises, and async-await? Which one is better and how to implement these?

*Also something extra for Angular Developers on what are Observables and how do they differ from Promise.*

First thing first, JavaScript by default runs synchronously but then why do we need to run it asynchronously? For this to understand, let’s see how actually is the synchronous operation performed in JS.

Guess what will be the output for the below code block.

console.log('a');
function callMe() {
console.log('b');
}
callMe()
console.log('c');

Easy, right? The output will be, a b c.

But how does this actually work? We need to first understand that JavaScript is a single-threaded programming language, which means it can perform only one action at a time.
So what happens behind the scene is that our first console.log(‘a’) gets added to the call-stack and then we print the output, in this case, ‘a’. So in short it executes the function present in the call-stack, logs the output, and removes this function from the stack. Then callMe function gets added to our call-stack and it returns the value of ‘b’ and gets removed and so on.

Now here the function callMe just adds a logger. What if there is a function that needs to run thousands of lines of code to return a value or an AJAX call which will take some time to bring back our data? Will we wait for the function to execute completely and then move ahead?
This will be a nightmare. JavaScript waits for NO ONE. And this is when Async JS comes to the picture.

Consider another example where we run async codes.

console.log('a');
setTimeout(() => {
console.log('b');
}, 1000);
console.log('c');
//setTimeout is a function provided by JS which does async operation

Now as you might have expected the out will print ‘a’ then ‘c’ and after one second it prints ‘b’, but what if we pass 0 seconds as time in our setTimeout function?

console.log('a');
setTimeout(() => {
console.log('b');
}, 0);
console.log('c');

Again, the output will be the same, but why? The reason is that any async function is not added to the call-stack, but to something called as Web Apis (provided by the browser, and we are running our JS in browser). So JS passes this setTimeout function in Web Apis, and not in call-stack. Again, as JS is single-threaded, it runs the next line of code. When all the synchronous processes are completed only then, we add the functions present in Web Apis to our call-stack and then execute these. So even if we pass 0 seconds in our setTimeout function, it doesn’t get added to our call-stack directly, but to the Web APIs.

As we now know how async operation runs behind the scene, let us talk about what issue we might face when we try to run a service call (faking it using setTimeout) synchronously.
Consider an example that a user logins and when it does, it should get a token.

console.log('a');function login(email, password) {
setTimeout(() => {
return {usertoken: "randomId"}
},3000);
}
const token = login("abc@gmail.com" , "1234");console.log(token);console.log('c');

Will I be getting my user token in this case?
The answer to this is no, when we call the login function, we don’t wait for its output, JS moves ahead and tries to log the token value, which is undefined in this case. But then question here is how do I get my token?

Moving forward we will look on three ways to achieve this and kicking on on list is:

Good old callbacks

Let us look at what the definition of callback really is:

A callback function is a function passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action.

And what the heck did you just read!!!! Please don’t burst your head reading this again and again if you didn't understand what the definition actually says. Try understanding the below example and then come back and read the definition again!

Coming back to our login function again.
The first change we will have to do is pass another parameter in our login function, which will be our callback function.

function login(email, password , callback)

Now where we call the login function we have to add another parameter for that callback, which will be an anonymous function.

const token = login(“abc@gmail.com” , “1234” , () => {})

Now inside the function login, instead of returning the token, we pass that value of the token in our callback function.

function login(email, password , callback) {
setTimeout(() => {
callback({usertoken: "randomId"});
},3000);
}

That’s it, now the value (userToken) passed as an argument in the callback function can be received by the anonymous function, and we can access the
token value.
So our final code will look like this:

console.log('a');
function login(email, password , callback) {
setTimeout(() => {
callback({usertoken: "randomId"});
},3000);
}
const token = login("abc@gmail.com" , "1234", (userTokenFromCB) => {
console.log("usertoken" , userTokenFromCB.usertoken);
});
console.log('c');

Our output here will be “a” then “c” and after three seconds we will log “randomId” which is basically our userToken.

So this is how callback helps us in solving the issue. But then why do we need another approach? What is the problem with not only using callback? Let’s find out.

Now suppose after my user is logged in, I need to pass my token to fetch the user details. How will my callback code look then?

console.log("a");function login(email ,password, callBack) {
setTimeout(() => {
callBack({userToken: "randomId"});
}, 3000);
}
function getDetails(userToken , callBack) {
setTimeout(() => {
callBack({name: "John Doe", dob: "6th May 1995"})
}, 3000);
}
const details = login("abc@gmail.com", "1234",(userTokenFromCB) => {
const token = userTokenFromCB.userToken;
getDetailsProfile( token ,(detailsFromCB) => {
console.log('Details', detailsFromCB);
});
});
console.log("b");

Here we see that a chain of callback functions gets generated. Now if suppose we need the details of the user to fetch some other API (not applicable in our example) a huge chain of callback functions get generated and this is called callback hell.

Another problem that we face with callbacks is error handling, what if the API that we call somehow fails? To handle these errors we again have to use the callback function and you can guess where I am going with this. Also, the code readability is not so much clear when we use callbacks.

Promise to our rescue

With ES6 came promises. Promises are object which basically gives us a success of an async operation (resolve) or failure of an async operation (reject)

Here is how we define a promise.

const promise = new Promise((resolve , reject) => {});

Converting login function using Promise:
Here instead of passing token inside a callback function, we pass the token inside resolve, which is provided to us by Promise.

function login(email , password) {
return new Promise((resolve , reject) => {
setTimeout(() => {
resolve({userToken: "randomId"})
}, 3000)
})
}

Then to consume a promise we use .then

login("abc@gmail.com" , '1234')
.then(token => console.log(token));

So now if we need to call a chain of APIs this is how our promise will look

login("abc@gmail.com" , "1234")
.then(token => getUserDetails(token.userToken))
.then(details => getFriendList(details));
.then(friends => callAnotherAPI())
//and so on.

Now as we can see this becomes so much easier to read as we don’t have the daunting callbacks. Also to pass an error, instead of resolving it, we reject it and when we consume that promise we can use .catch to throw an error message.

Just a side note: What basically the above lines of code do is, it takes three-second (because of setTimeout) to get the token, then it uses that token and takes three more seconds to get the details, and so on. This happens because my getDetails function is depended on the return value of the login function.

But what if I don’t need to wait, I mean suppose we need to call two different and independent service at the same time (the response of one is not related to the other). Then we can use something called Promise.all() and we pass both(or multiple promises) as an array.

Promise.all([Promise1 , Promise2]).then(result=>console.log(result))

Promises are really great, it solves the issue of callback hell, it deals with error handling in a great way. But then why do we even need anything else to implement Async JS?
Well, people had some issues using “.then”, they thought it is still a kind of callback.
Now what everyone ( well not exactly everyone, people with high demands :P) wanted was to run async code but kind of written in a synchronous way. No
.then, no callbacks, none of this. What do we use now?

Async Await to save our day

Async await is still based on promises, but it just has kind of new syntax to work with.

Wouldn’t it be nice to write our code something like this?

const token = login("abc@gmail.com" , "1234");
const details = getDetails(token);

How can we run this asynchronously though? Thanks to ES7 which introduced us to the most modern way of controlling async operations, Async Await.

So as mentioned before, we will have to declare our functions using Promises. Nothing gets changed there, the change is only when we consume these promises.

function login(email , password) {
return new Promise((resolve , reject) => {
setTimeout(() => {
resolve({userToken: "randomId"})
}, 3000)
})
}
function getDetails(userToken) {
return new Promise((resolve , reject) => {
setTimeout(() => {
resolve({name: "John Doe", dob: "6th May 1995"})
}, 3000)
})
}

Now to get the token and the details, the first thing we do is create a function and get the token and the details inside that function.

function userDetails() {
const token = login("abc@gmail.com" , "1234");
const details = getDetails(token);
}
userDetails();

This as said before will not work, the next thing we do to make this work is to write await before login and getDetails.

function userDetails() {
const token = await login("abc@gmail.com" , "1234");
const details = await getDetails(token);
}
userDetails();

It still won’t work, we need to inform JS that we need to run the function userDetails asynchronously. To that we have our final step, write async before the function userDetails()

async function userDetails() {
const token = await login("abc@gmail.com" , "1234");
const details = await getDetails(token);
console.log(details);
}
userDetails();

Now, this looks easy and clear to read.
But how can we do the error handling here? We don’t have access to “.catch”.

It’s simple, we use our normal try and catch block. JS tries to run the code inside the try block if it doesn’t work or it gets an error, it goes to the catch block.

async function userDetails() {
try {
const token = await login("abc@gmail.com" , "1234");
const details = await getDetails(token);
console.log(details);
}
catch(err) {
console.log(err);
}
}
userDetails();

RxJs Observables

RxJS Observable is not a native JS feature but is provided by a third-party library called “RxJs”.

In an Observable patter we have an Observable and an Observer, in between, we have a stream of data or timeline and on this timeline, we can have multiple events emitted by Observables.
The observable give us three data which we have to look up to, “Handle Data”, “Handle Error”, and “Handle Completion”. The observer on the other hand has the task of being informed about these three data and act accordingly.
Observer then has some built-in methods to use,
“.next”, to emit new values, “.error” to throw an error, and “.complete”, to let the observer know that we are done.

To create an observable we use Rx.Observable.create provided to us by RxJs.
.create” takes in a function, which in turn takes observer as an argument.

function login(email , password) {
return Rx.Observable.create((observer) => {
setTimeout(() => {
observer.next({userToken: "randomId"})
}, 3000)
})
}
function getDetails(userToken) {
return Rx.Observable.create((observer) => {
setTimeout(() => {
observer.next({name: "John Doe", dob: "6th May 1995"})
}, 3000)
})
}

Unlike “.then” which use to execute a promise, we use “.subscribe” to listen to an observable

login("abc@gmail.com" , "1234")
.subscribe(token => getUserDetails(token.userToken))
.subscribe(details => getFriendList(details))
//and so on

The “.subscribe” taken in second parameter to handle errors

login("abc@gmail.com" , "1234").subscribe(token => {
console.log(token);
}, error => {
console.log(error);
)

Now the question, here again, is how are Observables different from Promises?
The core distinction between Promises and Observables is that Promises handle only one value, we send an HTTP request, we get a response, we resolve our Promise and then we are done. Observables on the other handle streams of data, we can wrap an Observable inside a click listener to listen to every new click and emit a new value on every new click, which is not possible by using Promise.

CONCLUSION

This is it. We now have learned how async JS works. Believe me, the chances of getting a question about this in an interview is immensely high. But be sure to explain everything with an example and not what the actual definition says. Hope this helps. :). All the best.

--

--