Let's all be honest and address the elephant in the room. As a beginner developer, asynchronous programming is one of the hardest and confusing JavaScript concepts to grasp.
A bit out of context, but can't we agree that the async concept can be as confusing as Bruce Banner didn't know there's an Ant-Man and a Spider-Man? 😅
I struggled to learn Promises
and async
and await
in the past few months, and I have seen async
and await
in my interviews. I decided to write a series of Async JS to break this struggle and hopefully will help anyone who is trying to understand asynchronous programming in JavaScript.
There are 4 parts in this series:
- Intro to Asynchronous JS (this article)
Promises
- More
Promises
async/await
Introduction
Before we start talking about Promises
, async
, await
, we need to first understand why do we care about handling asynchronously?
(Disclaimer: I understand the importance of learning how async works in both client-side and server-side. But in this article, I will mainly focus on client-side JS rather than server-side. I would like to write more about server-side in the future.)
First off, we have to understand JavaScript is always synchronous and single-threaded. In other words, when one block of code is being executed, no other block of code will be executed.
As you can see from above, the console printed the values according to the order.
JavaScript programs in web browser is typically event-driven. In other words, JavaScript is not going to do anything until the user tap or click on something. That's the client side. As for the JS-based server side, it usually waits for client requests to arrive over internet before doing anything.
We would use asynchronous JS in cases like fetching or accessing some kind of resource from a third party API.
Say you have a pretty large image on top of your website from a server, if we follow the JS synchronous style, the web browser has to completely finish loading the image before loading the rest of the content. For user experience, this behavior is not ideal, because you don't know how long the image will take to load.
If we use the fetch
method to fetch the image from a server for the website, since fetch
is asynchronous, when running the next line, it will throw an error as the response is not yet available (I PROMISE -- pun intended -- this will make more sense later on).
(You probably notice the images/GIFs in this article took a little bit time to load while the text is available -- a real example of asynchronous programming)
Asynchronous Programming with Callbacks
Before we dive into Promise
(will be introduced in the next article), the most fundamental concept we have to understand is callbacks (passing another function in a function and will be invoked when some condition is met or some event is occured). This is also the old-fashioned way of handling asynchronous programming before the introduction of Promise
in ES6. But some of these callbacks are still commonly seen without Promise
.
Timers (setTimeOut()
)
Using the example above from the Introduction section, a quick refresher of what we want in the following order:
- ice cream
- boba tea
- iced coffee
- beach
What if I want boba tea after I go to beach, let's add setTimeOut()
and get it 2 seconds (1000 milliseconds = 1 second) after I go to beach? Let's see how it looks like:
As you can see, "boba tea" appears ~2 sec after everything is printed on the console!
The first argument of setTimeOut()
is a callback function and the second argument is a time interval measured in milliseconds.
There's another type of timer function called setInterval()
. It is useful if you want a function to run repeatedly, but I will not cover in this article. Feel free to check this out here for more info about setInterval()
on MDN.
Events
Speaking of events, you probably heard of addEventListener()
. As mentioned in the intro, client-side JavaScript programs are almost universally event-driven. The web browser invokes these callback functions whenever a specified event occurs (as you may be familiar with hovering, clicking a mouse button, pressing a key on the keyboard). These callback functions are known as event listener and event handler.
addEventListener()
is the method to perform these callback functions based on specified event in a specified content. The second parameter of addEventListener()
method is an example of async callback.
Here is the example from W3Schools:
let btn = document.getElementById("myBtn");
// a callback function to be invoked when the user clicks on
// that button
btn.addEventListener("click", () => {
document.getElementById("demo").innerHTML = "Hello World";
});
Here's what happened, when a user clicks on a button that represent the HTML <button>
element that has an ID myBtn
, the text "Hello World" will show up.
The callback function is not immediately executed. Once any specified event occurs (in this case is "clicking"), the callback function will be performed asynchronously somewhere inside the HTML body.
✨ Pause for this iconic MCU GIF before we get to the final callbacks ✨
(I also needed to take a quick tea break here 😬)
Network Events/XMLHttpRequest
Last but not least, fetching data from a web server is another common source of asynchrony in JS programming (Like the example of fetching a large image I mentioned earlier in the intro section).
We would use an API object called XMLHttpRequest
to interact with servers.
According to MDN,
XMLHttpRequest (XHR) objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing. XMLHttpRequest is used heavily in AJAX programming.
Here's how it looks like (from MDN):
function loadData(url, type, callback) {
let request = new XMLHttpRequest();
request.open("GET", url);
response.responseType = type;
request.onload = function() {
callback(request.response);
};
request.send();
}
function displayImg(pic) {
let objectURL = URL.createObjectURL(pic);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
loadData('starbucks.jpg', 'pics', displayImg);
Quick breakdown:
We separate two functions:
-
loadData
to fetch the data from another server withXMLHttpRequest
anddisplayImg
to create an image to display the fetched data. - We then take the
displayImg
as a callback function, as well as the URL and the content type. As the web browser loads the JS program, theXMLHttpRequest
class plus the callback function would handle the server's response asynchronously and make HTTP request.
I know that's a lot to learn, but understanding the fundamental of callbacks helps understanding why Promises
was introduced. In the next article, we will look into using Promises
to simplify asynchronous programming.
See you in the next article!
Resources
🌟 Asynchronous JavaScript (MDN)
🌟 Eloquent JavaScript Chapter 11: Asynchronous Programming
🌟 JavaScript The Definitive Guide by David Flanagan (7th Edition) Chapter 13: Asynchronous JavaScript (Pg. 341 - 344) (Amazon)
Top comments (3)
Thank you so much. Promises and callbacks are a really hard topic for me. I'll follow you and your articles to learn more about them.
Of course and I completely understand! I still had to refer to a lot of resources to help me write these articles!! The rest of the series will be published in the next 48 hours, thanks for the support and comment!
Great Work Megan 😃 Loved Reading Your Content on Async Js