Trailer for the talk
Here's a sneak peek of the talk: Learn asynchronous JavaScript conceptually and practically with Tapas through live coding examples.
About the talk
Contrary to the view of many JavaScript developers, asynchronous programming is more than simply using async and await. In this talk, we will explore how JavaScript works under the hood with real examples to help you program and debug critical issues with confidence.
This talk will cover
Explanations and live coding examples of important concepts such as:
- Synchronous and asynchronous code execution of JavaScript functions
- Promises, callbacks and async/await
- The basics of browser APIs
About the speaker
Tapas is a web developer, blogger, YouTuber, and mentor with 10+ years experience in JavaScript and its frameworks/libraries. He works as a UI Architect & Senior UX Manager at Microfocus. He's passionate about open source and knowledge sharing.
Highlights of the talk
I know how to use JavaScript async and await. Why learn more?
As long as you’re able to do your work with async/await, debug existing promises that were written by someone else, and understand what you’re doing, then you might not need to learn more. However, if you want to confidently answer questions about promises to your interviewer, or be more efficient as you debug and develop code written by others, it’s worth it to learn a little bit about asynchronous programming and how it works.
What are functions and function execution?
Most programming languages should have functions. Functions are lines of code that perform certain tasks, organized logically into groups, so they can be reused. In JavaScript, functions are first-class citizens. What that means is, if you open any JavaScript program or file, virtually everything that you see is in terms of functions. You can:
Create functions:
// Create a function
function f1() {
// Your Code Goes Here…
}
// Invoke a function
f1();
Assign functions to a variable, and
// Create a function and assign to a variable
const f1 = function() {
// Your Code Goes Here…
}
// Invoke a function
f1();
Pass functions as argument:
// Create a function with another function as argument
function f1(f2) {
// Your Code Goes Here…
}
// Invoke a function with a function as parameter
f1(function(){
// I am just another regular function
});
Return a function from a function
// Create a function that returns another function
function f1() {
// Your Code Goes Here…
return (){
// I am just another function
}
}
// Invoke a function that returns another function
const returnedFunc = f1();
// Invoking the returned function
returnedFunc ();
What happens when you invoke a function?
To understand asynchronous JavaScript, you also need to understand the synchronous part of JavaScript. What happens when the interpreter sees each line of code? In JavaScript, there is a Function Execution Stack, also known as the Call Stack. It keeps track of the execution of functions, and it goes one by one, as you already know about stacks.
In the diagram below, we have an example. There are 3 functions - f1, f2, and f3. If those functions are nested within each other like you can see on the left side, f3 will go in the stack first, followed by f2 and f1. We start executing f1 since it’s on the top, and once that’s done, it leaves the stack, and we proceed with executing f2 and f3. This is the synchronous part of JavaScript, which is crucial for us to understand before we step into asynchronous JavaScript.
What are the differences between synchronous and asynchronous JavaScript?
Much like the term asynchronous in real life, asynchronous JavaScript means things are not happening at the same time. Contrast that with synchronous, which means things happen one after another in succession.
Why would you want to use asynchronous JavaScript? For example, perhaps you want to fetch data somewhere, but you don’t want the JavaScript engine to halt as you wait. The strength of asynchronous JavaScript lies here - you can execute functions with a delay.
There are two primary trigger points: (1) browser APIs/Web APIs, and (2) promises (JavaScript async/await too). For browser APIs, let’s take the code example below:
function f1() {
console.log(‘f1’);
}
function f2() {
console.log(‘f2’);
}
function main() {
console.log(‘main’);
setTimeout(f1, 0);
f2();
}
main();
What would be the output of this code block? Let’s first look at the diagram below. In the code, main happens first, so it goes into the Call Stack and gets executed, like we talked about earlier. Then console.log(‘main’) also goes into the stack and executes. Next, we see setTimeout, which is a browser API. The argument f1 in setTimeout is called a “callback”, and it will be placed in the Callback Queue. Nothing is done with it at this point, and we proceed. We see f2, and it goes into the stack and gets executed. Now, when the Call Stack is empty, it will check the Callback Queue to see if there are any functions in it. If there are, the functions will be placed into the Call Stack and be executed. The mechanism that drives this whole process is called the Event Loop.
If we execute this code block, things will happen exactly like what we mentioned above. (Watch the recording to see an animation!) The output will be main, f2, f1.
Now we come to promises. Let’s use the story of Jack and Jill as an illustration. They went on a hill to “Fetch” water. There’s a “Promise” made to their grandparents that the water will be fetched. This promise can be fulfilled, where they got the water, or it can be rejected, where they failed to get the water.
In programming, promises work the same way. In a line-by-line code execution, something is waiting for a task to be fulfilled or come out with an error. The “something” can be as simple as fetching information - from a database, server, or filesystem - anything that is asynchronous. That’s when you’d use promises. A promise is a special object in JavaScript. When you create a new promise, it takes the executor function, and the executor function has two parameters called resolve and reject. When the promise is fulfilled, we call resolve to notify the caller that it’s done, and they can do something with the output. If there’s a problem, we call reject to notify the caller to do something with the error.