If code is considered synchronous, then the code runs just as it reads: top-to-bottom. Everything happens in the same order, one after the other. Asynchronous, on the other hand, means that the code is able to run concurrently.
Well, then? How does it work?
Before we can dig into that, we need a solid understanding of what the call stack, commonly referred to as the stack, is, and what it does for us.
We expect that when the function is called with
With functional programming, you can think of the stack like a pile, or stack of books or movies (we’ll get further into that later). However with simple, independent functions like these, it’s more akin to sliding a disc into your BluRay player. You can only play one disc at a time, and need to take the disc out before you can add another.
The same watchMovie() function is called three times, each time with a different argument to let us know which movie we should watch next. When the function is called, it is added to the stack, executed, then removed from the stack. Only when the function is removed from the stack will the execution continue from watchMovie('A New Hope') on to watchMovie('The Empire Strikes Back'). Throughout the above code, the call stack’s size does not exceed one.
The output we’d see in the console would look like this:
Watch A New Hope next!
Watch The Empire Strikes Back next!
Watch Return of the Jedi next!
Let’s take a look at a few more functions. This time, we’ll cause the stack to pile up a bit.
- printSquare(4) is called, and it’s added to the call stack.
- WithinprintSquare(4), the square(n) function is called. It’s added onto the top of the stack too.
- Within the square block, multiply(n, n) is called, and added onto the top of the stack.
At the moment, the call stack would look a bit like this:
Before we can actually run square(n) or printSquare(4), we need to work our way through the stack, from top to bottom. It first returns multiply(n, n), then square(n), then printSquare(4), where 16 is finally printed to the console.
Within reason, there’s nothing wrong with piling things up on the stack. It can hold just about anything you can throw at it. That said, it is possible to exceed the stack’s limit; what’s often called a stack overflow, or blowing the stack. When it happens, you’ll see an error message along the lines of Maximum call stack size exceeded—something that you probably came across once or twice while getting familiar with loops.
Web APIs, the callback queue, and event loop
The DOM, XMLHttpRequest, and setTimeout are examples of Web APIs
But wait, before we get too far in, why does this even matter? When would we need something to be asynchronous anyways?
So when does the code actually run? When does it get to jump onto the call stack and execute?
Not until the stack is clear.
All of your program’s synchronous code will hop on and off the call stack, while passing any async operations to the Web APIs, and callback queue. Finally, when the call stack is clear, and all synchronous code has been executed, the event loop begins passing the actions from the callback queue onto the stack, allowing each action to execute completely before passing the next onto the stack. When the current async operation clears from the stack, the event loop adds the next action from the callback queue onto the stack. This pattern continues to repeat until the stack and callback queue are both empty.