What are WeakMaps?

December 18, 2024

Task Queue

Instead, the successCallback is added to the Task Queue (also called the Callback Queue for this exact reason). The Task Queue holds Web API callbacks and event handlers waiting to be executed at some point in the future.

Okay so now the successCallback is on the task queue... But when does it get executed?

Event Loop

Finally, we get to the Event Loop! It's the responsibility of the Event Loop to continuously check if the Call Stack is empty.

Whenever the Call Stack is empty — meaning there are no currently running tasks — it takes the first available task from the Task Queue and moves this onto the Call Stack, where the callback gets executed.

The Event Loop continuously checks if the call stack is empty, and if that's the case, checks for the first available task in the Task Queue, and moves this to the Call Stack for execution.

Another popular callback-based Web API is setTimeout. Whenever we call setTimeout, the function call is pushed onto the Call Stack, which is only responsible for initiating a timer with the specified delay. In the background, the browser keeps track of the timers.

Once a timer expires, the timer's callback is enqueued on the Task Queue! It's important to remember that the delay specifies the time after which the callback is pushed to the Task Queue, not the Call Stack.


This means that the actual delay to execution might be longer than the specified delay passed to setTimeout! If the Call Stack is still busy handling other tasks, the callback would have to wait in the Task Queue.

So far, we've seen how callback-based APIs are handled. However, most modern Web APIs use a promise-based approach, and, as you may have guessed, these are handled differently.

I'll assume some basic knowledge about Promises from here on. My video on Promises could help if you need a refresher!

Microtask Queue

Most (modern) Web APIs return a promise allow us to handle the returned data through chaining promise handlers (or by using await) instead of using callbacks.

fetch("...")
  .then(res => ...)
  .catch(err => (...)

Next, the console.log is pushed to the Call Stack , and logs End of script to the console. In this case, the network request is still pending.

When the server finally returns the data, the [[PromiseStatus]] is set to "fulfilled" , the [[PromiseResult]] is set to the Response object. As the promise resolves, the PromiseReaction is pushed onto the Microtask Queue.

When the Call Stack is empty, the Event Loop moves the handler callback from the Microtask Queue onto the the Call Stack, where it's executed, logs the Response object, and eventually popped off the call stack.

Are all Web APIs handled asynchronously?

No, just the ones that initiate asynchronous operations. Other methods, for example document.getElementById() or localStorage.setItem() , are handled synchronously.

Recap

Let's recap what we covered so far:

  • JavaScript is single-threaded, meaning it can only handle one task at a time.

  • Web APIs are used to interact with features leveraged by the browser. Some of these APIs allow us to initiate async tasks in the background.

  • The function call that initiates the async task is added to the Call Stack, but that is just to hand it off to the browser. The actual async task is handled in the background, and does not remain on the Call Stack.

  • The Task Queue is used by callback-based Web APIs to enqueue the callbacks once the asynchronous task has completed.

  • The Microtask Queue is used by Promise handlers, async function bodies following await, MutationObserver callbacks andqueueMicrotask callbacks. This queue has priority over the Task Queue.

  • When the Call Stack is empty, the Event Loop first moves tasks from the Microtask Queue until this queue is completely empty. Then, it moves on to the Task Queue, where it moves the first available task to the Call Stack. After handling the first available task, it "starts over" by again checking the Microtask Queue.

Promisifying callback-based APIS

To enhance readability and manage the flow of async operations in callback-based Web APIs, we can wrap them in a Promise.

For example, we can wrap the Geolocation API's callback-based getCurrentPosition method in a Promise constructor.