JavaScript Unwrapped: From Basics to Brilliance - Synchronous vs Asynchronous JS
Topics: Difference between Synchronous and Asynchronous code
Synchronous vs. Asynchronous Code in JavaScript: The Battle of the Execution Flows!
Introduction
Hey there, fellow coders! 👋 Ever found yourself wondering why some parts of your JavaScript code seem to run in a straight line, while others feel like they're multitasking wizards? Well, buckle up because we’re diving into the world of synchronous and asynchronous code! These two concepts are like the tortoise and the hare of the JavaScript universe, each with its own way of getting things done. Let’s break it down!
Synchronous Code and the Call Stack: The Organized Queue
To really grasp how synchronous code works, let’s take a peek behind the scenes at one of JavaScript’s most crucial components: the call stack. Think of the call stack as a to-do list for your code—a neat and orderly queue where tasks are processed one by one.
What is the Call Stack?
The call stack is a data structure that tracks the functions you’re currently executing. When a function is called, it gets added to the top of the stack. Once that function finishes running, it’s removed from the stack, and JavaScript moves on to the next item. In synchronous code, only one function can run at a time, and nothing else happens until the current function finishes its job.
Example:
Let’s break down a simple synchronous example to see how the call stack works.
The Call Stack in Action 🎬
first() is called:
The
first()
function is added to the call stack.Inside
first()
, theconsole.log('First function!')
line runs, printing "First function!" to the console.Once done, the
first()
function is removed from the call stack.
Call Stack:
first()
After execution:
(empty)
second() is called:
Now,
second()
is added to the call stack.Inside
second()
, theconsole.log('Second function!')
line runs, printing "Second function!" to the console.After it finishes,
second()
is removed from the call stack.
Call Stack:
second()
After execution:
(empty)
third() is called:
Finally,
third()
is added to the call stack.Inside
third()
, theconsole.log('Third function!')
line runs, printing "Third function!" to the console.Once done,
third()
is removed from the call stack.
Call Stack:
third()
After execution:
(empty)
Diagrammatic Representation of the above execution
I am also including a video of the execution to have a better understanding how synchronous code works in JS
The Takeaway: Synchronous Code & the Call Stack 📝
In synchronous code, functions are processed one at a time, and each function must complete before the next one begins. The call stack ensures that JavaScript handles each function in the order they’re called, without skipping ahead or doing anything out of turn. This creates a predictable, linear flow of execution, making it easier to understand what your code is doing at any given moment.
However, this also means that if a function takes a long time to finish, everything else in the stack has to wait—just like when you’re stuck behind someone taking their sweet time at the grocery store checkout. That’s where asynchronous code comes in, letting you keep the line moving while waiting for slower tasks to complete.
Asynchronous Code: The Magic of Multitasking 🎩
While synchronous code executes tasks one by one in a single, orderly line, asynchronous code allows JavaScript to handle multiple tasks simultaneously without blocking the main thread. To understand this, we need to look at how the call stack, Web APIs, and the callback queue work together.
The Setup: Web APIs and the Callback Queue 🌐
Web APIs: JavaScript in the browser has access to Web APIs like
setTimeout
,fetch
, DOM events, and more. These are provided by the browser, not by JavaScript itself.Callback Queue: When an asynchronous task completes (like a timer or an HTTP request), it doesn’t go straight back to the call stack. Instead, it moves to the callback queue, where it waits for the stack to be empty before being executed.
Example:
Let’s walk through an example to see how these components interact.
The Asynchronous Process: Call Stack, Web APIs, and Callback Queue 🎢
Start with the Call Stack:
The
console.log('Start')
is called and added to the call stack.It executes immediately, printing "Start" to the console.
Once done, the
console.log('Start')
is removed from the call stack.
Call Stack:
console.log('Start')
-> (empty)
Output:
Start
Handling
setTimeout
(Web API):The
setTimeout
function is called next, and it’s added to the call stack.setTimeout
is a Web API, so the call stack passes this task off to the browser’s Web API environment. The browser starts the 2-second timer.The
setTimeout
is removed from the call stack as it’s now being handled by the Web API.
Call Stack:
setTimeout
-> (empty)
Web API (Timer Starts):
setTimeout
(2 seconds)
Continuing with Synchronous Code:
The
console.log('End')
is called and added to the call stack.It executes immediately, printing "End" to the console.
Once done, the
console.log('End')
is removed from the call stack.
Call Stack:
console.log('End')
-> (empty)
Output:
Start
End
Timer Completes, Moving to Callback Queue:
After the 2-second timer is up, the Web API passes the callback function (
console.log('This is from setTimeout!')
) to the callback queue.The callback queue holds this function until the call stack is empty.
Callback Queue:
console.log('This is from setTimeout!')
Processing the Callback Queue:
Once the call stack is empty (after
console.log('End')
is done), the event loop checks the callback queue.The
console.log('This is from setTimeout!')
function is moved from the callback queue to the call stack.
Call Stack:
console.log('This is from setTimeout!')
-> (empty)
Output:
Start
End
This is from setTimeout!
The video attachment below provides a visual to the above written actions.
The Takeaway: Asynchronous Code & the Event Loop 🔄
Asynchronous code lets JavaScript perform tasks without waiting for time-consuming operations to finish. The Web API handles these tasks in the background, and once complete, they’re queued up in the callback queue. The event loop ensures that these queued tasks are only executed after the call stack is empty, keeping everything running smoothly without blocking the main thread.
In the example, even though the setTimeout
callback had to wait 10 seconds, the rest of the code continued executing right away. This non-blocking behavior is key to writing efficient, responsive applications, especially when dealing with tasks like network requests or user interactions.
However, note that not all callback functions are asynchronous in nature!
Wrapping It All Up: Synchronous vs. Asynchronous 🎁
In the world of JavaScript, understanding the difference between synchronous and asynchronous code is like mastering the art of time management. Synchronous code is the straight-laced, rule-following worker that gets the job done one task at a time. It’s reliable and predictable but can get bogged down when a task takes too long.
On the flip side, asynchronous code is the multitasking wizard that keeps everything moving smoothly, even when some tasks take longer than others. Thanks to the call stack, Web APIs, and the callback queue, JavaScript can handle time-consuming tasks without freezing up the entire program. This non-blocking behavior is crucial for creating responsive, user-friendly applications.
Whether you’re dealing with simple console logs or complex API requests, knowing when to use synchronous vs. asynchronous code will help you write better, more efficient JavaScript. So next time you’re coding, remember: sometimes it’s okay to wait in line, but other times, it’s better to multitask like a pro. Happy coding! 🎉