Building on our knowledge of the event loop we will see how the execution timings of microtasks queued in event listeners can be different depending on how an event is dispatched.
Instructor: [0:00] A microtask is any piece of code that has been assigned to run after a task is completed or when the call stack is empty. In this example, we have a task to execute a callback function. The callback function is queuing a microtask to execute a function called first.
[0:18] There are a few different mechanisms to queue a microtask, but the queueMicrotask API is super clear. Our callback function then also queues another microtask to execute a function called second. When our callback function is finished and our callback task is complete, all microtasks in the microtask queue are run.
[0:41] Here, I am queuing a task with setTimeout() to log FutureTask to the console. Then I'm queuing a microtask with queueMicrotask which will log out microtask to the console. Then I have a console.log statement which will log out initial task.
[1:00] If we come over to the browser, we'll see in the console that initial task was logged first, then microtask, and then FutureTask. Let's see what's happening. When this body of code is initially executing, it will log out initial task to the console.
[1:16] When this body of code finishes, the call stack will be empty and our microtask will be added to the call stack and will log out microtask to the console. Then at a later point, the task we created with set timeout will be executed on the call stack and will log out FutureTask.
[1:37] When a task for an event is being executed on the call stack, all EventListeners for the event are executed. What is interesting to note is that for our clickTask, after every EventListener is executed, the call stack is empty.
[2:00] When the call stack is empty, our microtask queue will be executed. What this means is that microtasks queued within an EventListener will be executed immediately after that EventListener and before the next EventListener is executed.
[2:21] Here I am adding two clickEventListeners to the button. In the first EventListener, I'm logging out listener one to the console and then queuing a microtask which will log out microtask to the console. The second clickEventListener is logging out listener two to the console. When I click on the button, we see that listener one logs out, and then microtask, and then listener two.
[2:45] In our first EventListener, we log out listener one to the console. After the first EventListener is finished, the call stack for the clickEvent task is empty and so the microtask queue is executed which causes microtask to be logged out to the console.
[3:04] After this, our second EventListener is executed which causes listener two to be logged to the console. Things are different when we manually dispatch a synthetic event.
[3:18] Dispatching events is synchronous and all EventListeners on the event path will be executed. Our dispatchEvent call is in the call stack for this entire operation. What this means is that our call stack is not empty between our EventListeners.
[3:36] Because the call stack is not empty, microtasks queued in EventListeners will not be executed until after all the EventListeners are executed and either the task is complete or the call stack is empty.
[3:53] If we come back to this example, now let's dispatch our own clickEvent on the button. We now see a change in the order of our console log statements. Now, we see listener one, listener two, and then microtask.
[4:08] Whereas before it was listener one, microtask, listener two. In this case, both clickEventListeners are executed before the microtask that we queued in our first EventListener is executed.
[4:17] This is because our call to dispatchEvent was in the call stack and so the call stack was not empty between the execution of the EventListeners. When we manually dispatch our clickEvent, we can see that the microtasks are run after the EventListeners.
[4:35] Whereas when we click on the button, the microtasks queued inside of an EventListener will execute after that EventListener. The changed ordering of microtasks is something to be aware of when manually dispatching events.