javascript promises, the event loop, and the job queue - javascript

Consider the following code:
function foo() {
console.log('foo');
new Promise(
function(resolve, reject) {
setTimeout(function() {
resolve('RESOLVING');
}, 5000);
}
)
.then(
function(value) {
console.log(value);
}
);
}
foo();
I am trying to understand what happens here correctly:
on executing new Promise the "executer function" is run directly and when setTimeout is called, an operation to add a new entry to the "event queue" is scheduled (for 5 seconds later)
because of the call to then an operation to add to a "job queue" a call to the passed function (which logs to the console) is organised to happen after the Promise is resolved
when the setTimeout callback is executed (on some tick of the event loop), the Promise is resolved and based on point 2, the function argument to the then call is added to a "job queue" and subsequently executed.
Notice I say [a "job queue"] because there is something I am not sure about; which "job queue is it?". The way I understand it, a "job queue" is linked to an entry on the "event queue". So would that be the setTimeout entry in above example?
Assuming no other events are added to the "event queue" before (and after) setTimeout's callback is added, wouldn't the entry for the main code (the call to foo) have been (usually) gone (run to completion) by that time and thus there would be no other entry than setTimeout's for the then's "job queue" entry to be linked to?

on executing new Promise the "executer function" is run directly and when setTimeout is called, an operation to add a new entry to the "event queue" is scheduled (for 5 seconds later)
Yes. More specifically, calling setTimeout schedules a timer with the browser's timer mechanism; roughly five seconds later, the timer mechanism adds a job to the main job queue that will call your callback.
because of the call to then an operation to add to a "job queue" a call to the passed function (which logs to the console) is organised to happen after the Promise is resolved
Right. then (with a single argument) adds a fulfillment handler to the promise (and creates another promise that it returns). When the promise resolves, a job to call the handler is added to the job queue (but it's a different job queue).
when the setTimeout callback is executed (on some tick of the event loop), the Promise is resolved and based on point 2, the function argument to the then call is added to a "job queue" and subsequently executed.
Yes, but it's not the same job queue. :-)
The main job queue is where things like event handlers and timer callbacks and such go. The primary event loop picks up a job from the queue, runs it to completion, and then picks up the next job, etc., idling if there are no jobs to run.
Once a job has been run to completion, another loop gets run, which is responsible for running any pending promise jobs that were scheduled during that main job.
In the JavaScript spec, the main job queue is called ScriptJobs, and the promise callback job queue is PromiseJobs. At the end of a ScriptJob, all PromiseJobs that have been queued are executed, before the next ScriptJob. (In the HTML spec, their names are "task" [or "macrotask"] and "microtask".)
And yes, this does mean that if Job A and Job B are both queued, and then Job A gets picked up and schedules a promise callback, that promise callback is run before Job B is run, even though Job B was queued (in the main queue) first.
Notice I say [a "job queue"] because there is something I am not sure about; which "job queue is it?"
Hopefully I covered that above. Basically:
Initial script execution, event handlers, timers, and requestAnimationFrame callbacks are queued to the ScriptJobs queue (the main one); they're "macrotasks" (or simply "tasks").
Promise callbacks are queued to the PromiseJobs queue, which is processed until empty at the end of a task. Thatis, promise callbacks are "microtasks."
The way I understand it, a "job queue" is linked to an entry on the "event queue".
Those are just different names for the same thing. The JavaScript spec uses the terms "job" and "job queue." The HTML spec uses "tasks" and "task queue" and "event loop". The event loop is what picks up jobs from the ScriptJobs queue.
So would that be the setTimeout entry in above example?
When the timer fires, the job is scheduled in the ScriptJobs queue.
Assuming no other events are added to the "event queue" before (and after) setTimeout's callback is added, wouldn't the entry for the main code (the call to foo) have been (usually) gone (run to completion) by that time and thus there would be no other entry than setTimeout's for the then's "job queue" entry to be linked to?
Basically yes. Let's run it down:
The browser loads the script and adds a job to ScriptJobs to run the script's top-level code.
The event loop picks up that job and runs it:
That code defines foo and calls it.
Within foo, you do the console.log and then create a promise.
The promise executor schedules a timer callback: that adds a timer to the browser's timer list. It doesn't queue a job yet.
then adds a fulfillment handler to the promise.
That job ends.
Roughly five seconds later, the browser adds a job to ScriptJobs to call your timer callback.
The event loop picks up that job and runs it:
The callback resolves the promise, which adds a promise fulfillment handler call to the PromiseJobs queue.
That job ends, but with entries in PromiseJobs, so the JavaScript engine loops through those in order:
It picks up the fulfillment handler callback job and runs it; that fulfillment handler does console.log
That job is completely done now.
More to explore:
Jobs and Job Queues in the JavaScript specification
Event Loops in the HTML5 specification

Related

How Does the JavaScript Interpreter add Global Statements to the Event Queue?

I am not sure how statements in the global scope are placed into the JavaScript event queue. I first thought that the interpreter went through and added all global statements into the event queue line by line, then went and executed each event, but that logic does not line up with the example given below. How does the JavaScript interpreter add global statements to the event queue, and why is the output from the two examples given below different?
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() => {resolve("1")}, 0)
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
The console output to the above code is
1
2
Now consider this example. Here, the difference is on the body of the promise callback, as there is a nested setTimeout
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() = > {setTimeout(() => {resolve("1")}, 0)}, 0);
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
The console output to the above code is
2
1
What I don't understand is the order in which things are added to the event queue. The first snippet implies that the promise p will run, and then during its execution, resolve is put in the event queue. Once all of p's stack frames are popped, then resolve is run. After than p.then(...) is run, and finally the last console.log("2");
In the second example, somehow the number 2 is being printed to the console before the number 1. But would things not be added to the event queue in this order
1.) p
2.) setTimeout( () => {resolve("1")}, 0)
3.) resolve("1")
4.) p.then(...)
5.) console.log("2")
I clearly have some sort of event queue logic wrong in my head, but I have been reading everything I can and I am stuck. Any help with this is greatly appreciated.
There are several confusing things in your question that I think show some misconceptions about what is happening so let's cover those initially.
First, "statements" are not ever placed into the event queue. When an asynchronous task finishes running or when it is time for a timer to run, then something is inserted in the event queue. Nothing is in the queue before that. Right after you call setTimeout(), before the time has come for the setTimeout() to fire there is nothing in the event queue.
Instead, setTimeout() runs synchronously, configures a timer in the internals of the JS environment, associates the callback function you passed to setTimeout() to that timer and then immediately returns where JS execution continues on the next line of code. Sometime later when the time has been reached for the timer to fire and control has returned back to the event loop, the event loop will call the callback for that timer. The internals of exactly how this works vary a bit according to which Javascript environment it is, but they all have the same effect relative to other things going on in the JS environment. In nodejs, for example, nothing is ever actually inserted into the event queue itself. Instead, there are phases of the event loop (different things to check to see if there's something to run) and one of the phases is to check to see if the current time is at or after the time that the next timer event is scheduled for (the soonest timer that has been scheduled). In nodejs, timers are stored in a sorted linked list with the soonest timer at the head of the list. The event loop compares the current time with the timer on the timer at the head of the list to see if its time to execute that timer yet or not. If not, it goes about its business looking for other types of events in the various queues. If so, it grabs the callback associated with that timer and calls the callback.
Second, "events" are things that cause callback functions to get called and the code in that callback function is executed.
Calling a function that may then cause something to be inserted into the event queue, either immediately or later (depending upon the function). So, when setTimeout() is executed, it schedules a timer and some time later, it will cause the event loop to call the callback associated with that timer.
Third, there is not just a single event queue for every type of event. There are actually multiple queues and there are rules about what gets to run first if there are multiple different types of things waiting to run. For example, when a promise is resolved or rejected and thus has registered callbacks to call, those promise jobs get to run before timer related callbacks. Promises actually have their own separate queue for resolved or rejected promises waiting to call their appropriate callbacks.
Fourth, setTimeout(), even when given a 0 time, always calls its callback in some future tick of the event loop. It never runs synchronously or immediately. So, the rest of the current thread of Javascript execution always finishes running before a setTimeout() callback ever gets called. Promises also always call .then() or .catch() handlers after the current thread of execution finishes and control returns back to the event loop. Pending promise operations in the event queues always get to run before any pending timer events.
And to confuse things slightly, the Promise executor function (the callback fn you pass as in new Promise(fn)) does run synchronously. The event loop does not participate in running fn there. new Promise() is executed and that promise constructor immediately calls the executor callback function you passed to the promise constructor.
Now, lets look at your first code block:
let handleResolved = (data) => {
console.log(data);
}
let p = new Promise((resolve, reject) => {
setTimeout(() => {resolve("1")}, 0)
});
p.then(handleResolved);
setTimeout(() => {
console.log("2");
}, 0);
In order, here's what this does:
Assign a function to the handleResolved variable.
Call new Promise() which immediately and synchronously runs the promise executor callback you pass to it.
That executor callback, then calls setTimeout(fn, 0) which schedules a timer to run soon.
Assign the result of the new Promise() constructor to the p variable.
Execute p.then(handleResolved) which just registers handleResolved as a callback function for when the promise p is resolved.
Execute the second setTimeout() which schedules a timer to run soon.
Return control back to the event loop.
Shortly after returning control back to the event loop, the first timer you registered fires. Since it has the same execution time as the 2nd one you registered, the two timers will execute in the order they were originally registered. So, the first one calls its callback which calls resolve("1") to cause the promise p to change its state to be resolved. This schedules the .then() handlers for that promise by inserting a "job" into the promise queue.
That job will be run after the current stack frame finishes executing and returns control back to the system.
The call to resolve("1") finishes and control goes back to the event loop.
Because pending promise operations are served before pending timers, handleResolved(1) is called. That functions runs, outputs "1" to the console and then returns control back to the event loop.
The event loop then calls the callback associated with the remaining timer and "2" is output to the console.
What I don't understand is the order in which things are added to the event queue. The first snippet implies that the promise p will run, and then during its execution, resolve is put in the event queue. Once all of p's stack frames are popped, then resolve is run. After than p.then(...) is run, and finally the last console.log("2");
I can't really respond directly to this because this just isn't how things work at all. Promises don't "run". The new Promise() constructor is run. Promises themselves are just notification machines that notify registered listeners about changes in their state. resolve is not put in the event queue. resolve() is a function that gets called and changes the internal state of a promise when it gets called. p doesn't have stack frames. p.then() is run immediately, not later. It's just that all that p.then() does is register a callback so that callback can then be called later. Please see the above 1-11 steps for the sequence of how things work.
In the second example, somehow the number 2 is being printed to the console before the number 1. But would things not be added to the event queue in this order
In the second example, you have three calls to setTimeout() where the third one is nested inside the first one. This is what changes your timing relative to the first code block.
We have mostly the same steps as the first example except that instead of this:
setTimeout(() => {resolve("1")}, 0)
you have this:
setTimeout(() = > {setTimeout(() => {resolve("1")}, 0)}, 0);
This means that the promise constructor is called and this outer timer is set.
then, the rest of the synchronous code runs and the last timer in the code block is then set. Just like in the first code block, this first timer will get to call its callback before the second one. But, this time the first one just calls another setTimeout(fn, 0). Since timer callbacks are always executed in some future tick of the event loop (not immediately, even if the time is set to 0), that means that all the first timer does when it gets a chance to run is schedule another timer. Then, the last timer in the code block gets it's turn to run and you see the 2 in the console. Then, when that's done, the third timer (the one that was nested in the first timer) gets to run and you see the 1 in the console.
If we break down the second case so that each function is on its own we end up with
const handleResolved = (data) => {
console.log(data);
}
const promiseBody = (resolve, reject) => setTimeout( innerPromiseTimeout, 0, resolve );
const innerPromiseTimeout = (resolve) => setTimeout( resolveWith1, 0, resolve );
const resolveWith1 = (resolve) => resolve("1");
const timeoutLog2 = () => {
console.log("2");
};
// beginning of execution
// timers stack: [ ]
// promiseBody is executed synchronously
let p = new Promise( promiseBody );
// timers stack: [ innerPromiseTimeout ]
// this will happen only after resolveWith1 is called
p.then( handleResolved );
// timers stack: [ innerPromiseTimeout ]
setTimeout( timeoutLog2, 0 );
// timers stack: [ innerPromiseTimeout, timeoutLog2 ]
// some time later, innerPromiseTimeout is called
// timers stack: [ timeoutLog2, resolveWith1 ]
// timeoutLog2 is called
// timers stack: [ resolveWith1 ]
// resolveWith1 is called and then is executed in next microtask checkpoint
// timers stack: [ ]
Also note that setTimeout still has a minimum of 1ms in Chrome (they will soon remove it, but for the time being, it's there), so don't assume setTimeout(fn,0) will execute as the next task

When is an ES6 promise fulfillment handler table examined?

Is it true that the resolve() function and then() function below are the only functions in the ES6 Promise mechanism that look at and add the fulfillment handlers to the job queue (and mark them as already added and never again)?
let promise = new Promise(function(resolve, reject) {
console.log("resolve, reject are", resolve, reject);
setTimeout(function() {
resolve("this is the success data");
}, 1000);
});
promise.then(
function(a) {
console.log("Success.", a);
promise.then(function(a) { console.log("Go have a drink.", a); });
},
function(a) {
console.log("failure", a)
}
);
An alternative to the above code is, the resolve() is done directly, without being in the setTimeout(). Is it true that the then(fn) will add the fn to the fulfillment handler table (not job queue) for this promise, or if the promise has already been resolved, directly add the fn to the job queue, and the fn is marked as "already added to job queue" and no need to be added again?
On the other hand, the resolve() is a JS ES6 provided function, that mark a promise as "resolved" (state), and check if the fulfillment handler table for this promise is empty or not. If there are such handlers (and should not have been marked already added to the job queue before), then these handlers are added to the job queue and marked as already added.
And in any event, suppose there are fn1, fn2, fn3, ... etc in the fulfillment handler table, each one is added to the job queue as fn1(resolvedValue), where the resolvedValue is remembered internally by the resolve(resolveValue) call?
And there are no other situation where the fulfillment handler table is accessed at all. The job queue jobs are executed by the JS event loop, so that when all the GUI "click event handlers", etc are done, then the fulfillment (and rejection) handlers in the job queue are executed, and then also the setTimeout and setInterval handlers are also executed? Is this how the fulfillment handler table and job queue work?
Is it true that the resolve() function and then() function below are the only functions in the ES6 Promise mechanism that look at and add the fulfillment handlers to the job queue?
Yes. (reject also looks at the table and removes handlers without adding them to the job queue).
Is it true that the then(fn) will add the fn to the fulfillment handler table (not job queue) for this promise, or if the promise has already been resolved, directly add the fn to the job queue?
Yes.
and the fn is marked as "already added to job queue" and no need to be added again?
No, the fn is not marked at all. It could be passed multiple times to then, in which case it would need to be run multiple times.
But yes, the fulfillment handler table is cleared when the promise fulfills and the callback jobs are scheduled.
And in any event, suppose there are fn1, fn2, fn3, ... etc in the fulfillment handler table, each one is added to the job queue as fn1(resolvedValue), where the resolvedValue is remembered internally by the resolve(resolveValue) call?
Yes. Although technically resolve does not always fulfill the promise.
And there are no other situation where the fulfillment handler table is accessed at all.
Yes. I wonder why you care though.
The job queue jobs are executed by the JS event loop, so that when all the GUI "click event handlers", etc are done, then the fulfillment (and rejection) handlers in the job queue are executed, and then also the setTimeout and setInterval handlers are also executed? Is this how the fulfillment handler table and job queue work?
Yes. However notice that click handlers, setTimeout and setInterval have a separate event queue from promise jobs.

How does the JS event loop behave when a Promise's resolution depends on setTimeout?

console.log('1')
setTimeout(() => {
console.log('2')
}, 0)
function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})
}
three().then(result => console.log(result))
console.log('4')
This code snippet outputs 1 4 2
This is the behavior I would expect based on my understanding of javascript's event loop and concurrency model. But it leaves me with some lingering questions.
Before getting to those questions, I'll first break down my understanding of this code snippet.
Why code outputs 1
no explanation needed
Why code outputs 4
the callback that outputs 2 gets loaded into the event queue (aka macro task queue) after 0ms, but doesn't get executed until the main call stack is emptied.
even if three was a promise that was immediately resolved, its code is loaded into the job queue (aka microtask queue) and wouldn't be executed until the main call stack is emptied (regardless of the contents of the event queue)
Why code outputs 2
after console.log(4) the main call stack is empty and javascript looks for the next callback to load on the main stack. It's pretty safe to assume that at this point, some "worker thread" had already put the callback function that outputs 2 onto the macro task queue. This gets loaded onto the stack and 2 is output.
Why code does NOT output 3
This is where its a little blurry for me. The function three returns a promise that is then-ed in the main thread. Callback functions passed through then are loaded onto microtask queue and executed before the next task in the macrotask queue. So while you might think it'll run before the callback that logs 2, its actually theoretically impossible for it to run at all. That's because the Promise is only resolved via the callback function of its setTimeout, and that callback function (because of setTimeout) would only run if the main execution thread (the same thread that's waiting for the promise to resolve) is empty.
Why does this bother me
I'm trying to build a complete theoretical mental model of how javascript handles concurrency. One of the missing pieces in that model is the relationship between network requests, promises, and the event loop. Take the above code snippet, and suppose I replace three's setTimeout with some sort of network request (a very common thing in async web development). Assuming that the network request behaves similarly to setTimeout, in that when the "worker thread" is done, a callback is pushed to the macro task queue, it's hard for me to understand how that callback even gets executed. But this is something that happens literally all the time.
Can someone help me understand? Do I have any missing gaps in my current understanding of js concurrency? Have I made an incorrect assumption? Does any of this actually make any sense? lol
Why code does NOT output 3
In this code:
function three() {
return new Promise(resolve => {
setTimeout(() => {
return new Promise(resolve => resolve('3'))
},0)
})
}
three().then(result => console.log(result))
You never resolve the first Promise that three() creates. Since that's the one that is returned form three(), then the .then() handler in three().then(...) is never called.
You do resolve the promise created inside the timer, but you're returning that promise only to the timer callback which does nothing.
If you change your code to this:
function three() {
return new Promise(resolve => {
setTimeout(() => {
resolve('3');
},0)
})
}
three().then(result => console.log(result))
Then, you would see the 3get output.
So, this doesn't have anything to do with the event loop or how it works. It has to do with not resolving the promise that three() returns so the .then() handler on that promise never gets called.
I'm trying to build a complete theoretical mental model of how javascript handles concurrency. One of the missing pieces in that model is the relationship between network requests, promises, and the event loop. Take the above code snippet, and suppose I replace three's setTimeout with some sort of network request (a very common thing in async web development). Assuming that the network request behaves similarly to setTimeout, in that when the "worker thread" is done, a callback is pushed to the macro task queue, it's hard for me to understand how that callback even gets executed. But this is something that happens literally all the time.
Network requests and promises and timers all go through the event loop. There are very complicated rules about how multiple events in queue at the same time are prioritized relative to one another. .then() handlers are generally prioritized first.
Think of the Javascript interpreter as this simplistic sequence.
Get event from event queue
If nothing in the event queue, sleep until something is in the event queue
Run callback function associated with the event you pull from the event queue
Run that callback function until it returns
Note, it may not be completely done with its work because it may
have started other asynchronous operations and set up its own
callbacks or promises for those. But, it has returned from the
original callback that started it
When that callback returns, go back to the first step above and get the next event
Remember that network requests, promises, timers and literally ALL asynchronous operations in node.js go through the event queue in this manner.
Why you assume that network request would behave as setTimeout?
It is a Promise which resolved() would go in microtasks

Unable to understand Promise then and catch behavior

The actual output (when seen in Firefox console) of the below code is
- error1
- error2
- continue1
- continue3.
Promise
.resolve()
.then(() => Promise.reject('error1'))
.catch(console.log)
.then(() => console.log('continue1'))
Promise
.resolve()
.then(() => Promise.reject('error2'))
.then(() => console.log('continue2'), console.log)
.then(() => console.log('continue3'))
As per my understanding of promises, it should be - error1 - continue1 - error2 - continue3.
Need help in understanding the actual output
First off, it is kind of pointless to attempt to understand a possible ordering issue between two completely independent promise chains. When programming, you should treat their order as indeterminate (and in fact, as soon as you have real asynchronous operations inside the promise chains which any real promise chain implementations would usually contain, the order will become indeterminate). If the order matters to you between the two chains, then you need to add some specific code to coordinate the actions of the two independent promise chains.
So, the observed order in your snippet is this:
error1
error2
continue1
continue3
Since you have no async operations in any of your .then() or .catch() handlers, it will simply alternate between the two promise chains (which is what it appears to be doing when you run your snippet). Here's how it would basically execute:
First Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Second Promise.resolve() executes which adds to the event queue an event to run its .then() handlers.
Your sequence of JS is done so control returns back to the event queue. First .then() handler is called which then inserts another event into the event queue to run the .catch() handler and returns control back to the event loop.
Second .then() handler is at the top of the event queue and gets to run
And so on...
Keep in mind that per the promise specification each successive .then() or .catch() handler must be run asynchronously (on the next turn of the event queue). The promise chain doesn't execute as far as it can in one fell swoop. It executes the next step, puts the step after that into the queue and then returns control to the event loop which pops the next event off the queue. This will cause control to rotate between different promise chains that aren't waiting for something else to complete.
But, I repeat, you should not be depending upon this level of execution order and, in more complicated real-world cases with other async operations involved, you could not event attempt to predict an order. You should assume that these two separate promise chains run in an indeterminate order beyond the synchronous part (each Promise.resolve()). And, then if any specific ordering matters to your code, you must add code to actually synchronize and control the execution order using Promise.all() or linking the chains together into one branched chain or one sequence chain or something like that depending upon the needs of the code.
Promises that resolve/reject put a microtask in the corresponding queue. That task involves calling any then/catch callback functions. The messages in that queue are processed in sequence.
So in your example the sequence is:
First Promise.resolve() is executed. The microtask queue now has 1 entry:
Queue = [promise #1 resolved]
Normal execution flow continues to the second Promise.resolve()
Second Promise.resolve() is executed. The microtask queue now has 2 entries:
Queue = [promise #1 resolved, promise #2 resolved]
Normal execution flow ends -- the call stack is empty.
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error1') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise #2 resolved, promise.then #1 rejected]
The first item in the queue is extracted and processed. The then callback () => Promise.reject('error2') is executed. This creates a new, rejected promise, and so a rejection is put on the queue
Queue = [promise.then #1 rejected, promise.then #2 rejected]
The first item in the queue is extracted and processed. It is a rejection, so the catch callback console.log is executed. This outputs "error1" and creates a new, resolved promise (with undefined value). The queue
Queue = [promise.then #2 rejected, promise.then.catch #1 resolved]
... etc
I am not completely sure the exact execution order of unrelated promises is specified anywhere, and even if it is, you probably want to introduce some explicit ordering dependency between the two promises if this is important to you.
But here is one possible and likely sequence:
Step one: Both promises are already resolved
Step two: Being resolved, both promises run their first then. This results in both being rejected. I would not depend on the order in which these two execute with respect to each-other.
Step three: Being rejected, both promises run their second catch/then. I would not depend on the order in which these two execute with respect to each-other.
You can probably depend on these steps going in that order (because it follows the queuing order of how these callbacks were submitted to the scheduler), but even that seems risky.
If you want to have the continue1 happen before the error2 you have to arrange code that waits for completion of the former.

What does microtask runs after every callback mean?

I'm reading this article on microtasks and there is the following conclusion:
In summary:
Tasks execute in order, and the browser may render between them
Microtasks execute in order, and are executed:
after every callback,
as long as no other JavaScript is mid-execution at the end of each
task
As I understand each task represents a single VM turn, so as long as no other JavaScript is mid-execution at the end of each task means when call stack is empty. But I don't understand what after every callback mean?
Can anyone please explain and show an example?
Here is the clear example that demonstrates this:
function cb1() {
console.log('cb1');
Promise.resolve('df').then(function promiseMicrotask() {
console.log('promise');
});
}
function cb2() {
console.log('cb2');
}
const element = document.querySelector('div.inner');
element.addEventListener('click', cb1);
element.addEventListener('click', cb2);
In the example above when you click on the div.inner, a browser schedules a task to handle the event and call the callbacks cb1 and cb2. Later it starts executing the task and triggers cb1. Inside cb1 a resolved promise schedules a microtask to run the promiseMicrotask callback. Whenever the current stack that started with cb1 is empty the browser checks the microtaks queue and finds a microtask promiseMicrotask. It triggers it and so it logs promise. Then it proceeds to triggering cb2.
So the microtask promiseMicrotask was processed after the callback cb1 but before cb2 and before the browser finished executing all callbacks in the current task.
What's important here is that all event callbacks are executed in current task.

Categories