Asynchronous JavaScript (Part 1): Unlocking the Power of Callbacks, Promises and Callback Queue
In our previous discussion, we learned about the JavaScript engine, a vital component of runtime development. Now, we'll dive into other crucial elements that aid in asynchronous execution in JavaScript.
As we know, JavaScript is a single-threaded language that can only execute one task at a time. However, in modern web development, we often encounter complex tasks that take time to complete, such as fetching data from an API or loading images. To handle such operations, we need a way to execute code asynchronously while keeping the main thread free.
In this post, we'll explore three key components that enable asynchronous execution in JavaScript:
Web APIs
Web APIs are typically provided by the browser or the runtime itself, and are used to interact with various web-based resources, such as network requests, timers, user interfaces, and more.
Some common Web APIs provided by JavaScript runtime environments include:
JavaScript runtime environments typically provide many other Web APIs as well, depending on the specific environment and its capabilities. Developers can use these APIs to build a wide variety of web applications, from simple web pages to complex web applications and games.
The Callback Queue
In JavaScript, there are two types of task queues: the callback queue and the microtask queue. Both queues hold tasks that are executed asynchronously, but there are some differences in how they work.
The Callback Queue (Macrostack Queue), also known as the task queue, holds tasks that are pushed to the queue by Web APIs, such as setTimeout, setInterval, XMLHttpRequest, or events like mouse clicks and keyboard inputs. When the call stack is empty, the event loop moves the first task from the callback queue to the call stack for execution.
For example, consider the following code:
console.log('1');
setTimeout(() => console.log('2'), 0);
console.log('3');
In this code, the console.log('1') statement is executed first, followed by the setTimeout function. The setTimeout function schedules the anonymous function passed to it to be executed after 0 milliseconds, which means that it will be added to the callback queue after the current call stack is empty. The console.log('3') statement is executed next.
When the current call stack is empty, the event loop moves the anonymous function from the callback queue to the call stack for execution. Therefore, the output of the above code will be:
Recommended by LinkedIn
1
3
2
On the other hand, the Microtask Queue, also known as the Job queue or Promise queue, holds tasks that are pushed to the queue by microtasks, such as Promise.resolve, Promise.reject, or queueMicrotask. When the call stack is empty and there are no pending tasks in the callback queue, the event loop moves the first task from the microtask queue to the call stack for execution.
For example, consider the following code:
console.log('1);
Promise.resolve().then(() => console.log('2'));
console.log('3');
In this code, the console.log('1') statement is executed first, followed by the Promise.resolve() function. The .then method schedules the anonymous function passed to it to be executed after the current call stack is empty, which means that it will be added to the microtask queue. The console.log('3') statement is executed next.
When the current call stack is empty and there are no pending tasks in the callback queue, the event loop moves the anonymous function from the microtask queue to the call stack for execution. Therefore, the output of the above code will be:
1
3
2
So, the main difference between the Callback Queue and the Microtask Queue is the order in which they are processed by the event loop. The Callback Queue holds tasks that are executed after the current call stack is complete, while the Microtask Queue holds tasks that are executed before the next task is executed.
What's next ?
In the second part of our exploration of asynchronous JavaScript, we'll be taking a closer look at the event loop and how the it manages the execution of tasks and events in the JavaScript runtime environment.
So, join me 👀 on this journey as we delve into the exciting world of advanced JavaScript concepts and stay informed about the next post series outlined in the roadmap above.
Thank you for your continued support and interest in my newsletter
Senior Software Engineer @ Zee5 (NextJS, ReactJS, JavaScript, HTML5, CSS3, Tailwind CSS, ReactNative)
1yHello Slimane, Thanks for breaking the important Javascript concepts into small (focused) articles through your series `The Depths of Javascript`, I read all your articles and they are really easy to read and understand. However, in this one I have a doubt, you wrote `When the current call stack is empty and there are no pending tasks in the callback queue, the event loop moves the anonymous function from the microtask queue to the call stack for execution. ` Does this mean the callback queue has a higher priority than the microtask queue? As per my understanding, the microtask queue has the highest priority. Let me know if I misinterpreted your statement or I would be happy to read more articles supporting the statement. Thanks.