How can Promise.resolve().then execute later? - javascript

Using native Javascript Promise:
Promise.resolve(1).then(it => console.log(it))
console.log(2)
This is logged:
2
1
Question: how is it possible for 2 to execute before 1? JS being event-driven, what is the event that is executing the callback given to then when the original caller has already left that execution tree? Is the engine doing some kind of behind-the-scene magic here?

JavaScript maintains something called a callstack. This is used to keep track of whereabouts in the script we are. When you call a function, it gets added to the callstack, and when the function returns/finishes, it gets removed/popped off the callstack. It is also helpful to think of your entire script also as being in its own "function", and so, when your script first begins its executing, we add "script" to the callstack:
Stack:
- Script
When your Promise resolves, it executes its associated .then() method and adds the callback to something called the micro-task queue. This queue (along with the macro-task queue) is used so that JavaScript can manage asynchronous events. As a result, once you run:
Promise.resolve(1).then(it => console.log(it))
the state of your queues/stacks looks like so (note, this is the state after Promise.resolve(1) and .then() have been added/popped off the callstack):
Stack:
- Script
Micro task queue:
- it => console.log(it)
The callback in the micro-task queue will only execute once it gets added to the Stack. This happens through the use of the event-loop. The event-loop will pull tasks off the micro-task queue only when the callstack is empty. Currently, we are still running "script", so the callstack isn't empty yet.
Next, you encounter your console.log(2), as this is a function call, it gets added to the call stack, and once it returns it gets popped off. This is the reason why you see 2 in the console appear first, as the Promise's .then() callback hasn't executed yet, as it's sitting in the micro-task queue waiting for the main script to finish. One the main script finishes, "script" gets popped off the stack:
Stack:
- (empty) <----------------<
| --- gets moved to the stack by the event-loop
Micro task queue: |
- it => console.log(it) ---^
the event loop then moves the task(s) from the micro-task queue now that the callstack is empty. Once the task is moved to the callstack, it executes, running the second console.log(it). This is why you see 1 logged in the console after the 2.

Related

Confused with Macrotasks and Microtasks [duplicate]

I've just finished reading the Promises/A+ specification and stumbled upon the terms microtask and macrotask: see http://promisesaplus.com/#notes
I've never heard of these terms before, and now I'm curious what the difference could be?
I've already tried to find some information on the web, but all I've found is this post from the w3.org Archives (which does not explain the difference to me): http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html
Additionally, I've found an npm module called "macrotask": https://www.npmjs.org/package/macrotask
Again, it is not clarified what the difference exactly is.
All I know is, that it has something to do with the event loop, as described in https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
and https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
I know I should theoretically be able to extract the differences myself, given this WHATWG specification. But I'm sure that others could benefit as well from a short explanation given by an expert.
One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification).
After this macrotask has finished, all available microtasks will be processed, namely within the same go-around cycle. While these microtasks are processed, they can queue even more microtasks, which will all be run one by one, until the microtask queue is exhausted.
What are the practical consequences of this?
If a microtask recursively queues other microtasks, it might take a long time until the next macrotask is processed. This means, you could end up with a blocked UI, or some finished I/O idling in your application.
However, at least concerning Node.js's process.nextTick function (which queues microtasks), there is an inbuilt protection against such blocking by means of process.maxTickDepth. This value is set to a default of 1000, cutting down further processing of microtasks after this limit is reached which allows the next macrotask to be processed)
So when to use what?
Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future).
Otherwise, stick to macrotasks.
Examples
macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
microtasks: process.nextTick, Promises, queueMicrotask, MutationObserver
Basic concepts in spec:
An event loop has one or more task queues.(task queue is macrotask queue)
Each event loop has a microtask queue.
task queue = macrotask queue != microtask queue
a task may be pushed into macrotask queue,or microtask queue
when a task is pushed into a queue(micro/macro),we mean preparing work is finished,so the task can be executed now.
And the event loop process model is as follows:
when call stack is empty,do the steps-
select the oldest task(task A) in task queues
if task A is null(means task queues is empty),jump to step 6
set "currently running task" to "task A"
run "task A"(means run the callback function)
set "currently running task" to null,remove "task A"
perform microtask queue
(a).select the oldest task(task x) in microtask queue
(b).if task x is null(means microtask queues is empty),jump to step (g)
(c).set "currently running task" to "task x"
(d).run "task x"
(e).set "currently running task" to null,remove "task x"
(f).select next oldest task in microtask queue,jump to step(b)
(g).finish microtask queue;
jump to step 1.
a simplified process model is as follows:
run the oldest task in macrotask queue,then remove it.
run all available tasks in microtask queue,then remove them.
next round:run next task in macrotask queue(jump step 2)
something to remember:
when a task (in macrotask queue) is running,new events may be registered.So new tasks may be created.Below are two new created tasks:
promiseA.then()'s callback is a task
promiseA is resolved/rejected: the task will be pushed into microtask queue in current round of event loop.
promiseA is pending: the task will be pushed into microtask queue in the future round of event loop(may be next round)
setTimeout(callback,n)'s callback is a task,and will be pushed into macrotask queue,even n is 0;
task in microtask queue will be run in the current round,while task in macrotask queue has to wait for next round of event loop.
we all know callback of "click","scroll","ajax","setTimeout"... are tasks,however we should also remember js codes as a whole in script tag is a task(a macrotask) too.
I think we can't discuss event loop in separation from the stack, so:
JS has three "stacks":
standard stack for all synchronous calls (one function calls another, etc)
microtask queue (or job queue or microtask stack) for all async operations with higher priority (process.nextTick, Promises, Object.observe, MutationObserver)
macrotask queue (or event queue, task queue, macrotask queue) for all async operations with lower priority (setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering)
|=======|
| macro |
| [...] |
| |
|=======|
| micro |
| [...] |
| |
|=======|
| stack |
| [...] |
| |
|=======|
And event loop works this way:
execute everything from bottom to top from the stack, and ONLY when the stack is empty, check what is going on in queues above
check micro stack and execute everything there (if required) with help of stack, one micro-task after another until the microtask queue is empty or don't require any execution and ONLY then check the macro stack
check macro stack and execute everything there (if required) with help of the stack
Micro stack won't be touched if the stack isn't empty. The macro stack won't be touched if the micro stack isn't empty OR does not require any execution.
To sum up: microtask queue is almost the same as macrotask queue but those tasks (process.nextTick, Promises, Object.observe, MutationObserver) have higher priority than macrotasks.
Micro is like macro but with higher priority.
Here you have "ultimate" code for understanding everything.
console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);
const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
setTimeout(() => {
console.log('stack [4]')
setTimeout(() => console.log("macro [5]"), 0);
p.then(() => console.log('micro [6]'));
}, 0);
console.log("stack [7]");
});
console.log("macro [8]");
/* Result:
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]
macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]
macro [5], macro [5], macro [5]
more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/
Macro tasks include keyboard events, mouse events, timer events (setTimeout) , network events, Html parsing, changing Urletc. A macro task represents some discrete and independent work. micro task queue has higher priority so macro task will wait for all the micro tasks are executed first.
Microtasks, are smaller tasks that update the application state and should be executed before the browser continues with other assignments such as
re-rendering the UI. Microtasks include promise callbacks and DOM mutation changes. Microtasks enable us to execute certain actions before the UI is re-rendered, thereby avoiding unnecessary UI rendering that might show an inconsistent application state.
Separation of macro and microtask enables the
event loop to prioritize types of tasks; for example, giving priority to performance-sensitive tasks.
In a single loop iteration, one macro task at most is processed
(others are left waiting in the queue), whereas all microtasks are processed.
Both task queues are placed outside the event loop, to indicate that the act of adding tasks to their matching queues happens outside the
event loop. Otherwise, any events that occur while JavaScript code is
being executed would be ignored. The acts of detecting and adding
tasks are done separately from the event loop.
Both types of tasks are executed one at a time. When a task starts executing, it’s executed to its completion. Only the browser can stop
the execution of a task; for example, if the task takes up too much
time or memory.
All microtasks should be executed before the next rendering because their goal is to update the application state before rendering occurs.
The browser usually tries to render the page 60 times per second,
It's accepted that 60 frames per second are the rate at which
animations will appear smooth. if we want to achieve smooth-running
applications, a single task, and all microtasks generated by that task
should ideally complete within 16 ms. If a task gets executed for more
than a couple of seconds, the browser shows an “Unresponsive script”
message.
reference John Resig-secrets of JS Ninja
I created an event loop pseudocode following the 4 concepts:
setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering are part of the macrotask queue. One macrotask item will be processed first.
process.nextTick, Promises, Object.observe, MutationObserver are part of the microtasks queue. The event loop will process all of the items in that queue including once that were processed during the current iteration.
There is another queue called animation queue which holds animation changes task items which will be processed next. All tasks which exists in this queue will be processed (not including new one which were added during the current iteration). It will be called if it is time for rendering
The rendering pipeline will try to render 60 times a second (every 16 ms)
while (true){
// 1. Get one macrotask (oldest) task item
task = macroTaskQueue.pop();
execute(task);
// 2. Go and execute microtasks while they have items in their queue (including those which were added during this iteration)
while (microtaskQueue.hasTasks()){
const microTask = microtaskQueue.pop();
execute(microTask);
}
// 3. If 16ms have elapsed since last time this condition was true
if (isPaintTime()){
// 4. Go and execute animationTasks while they have items in their queue (not including those which were added during this iteration)
const animationTasks = animationQueue.getTasks();
for (task in animationTasks){
execute(task);
}
repaint(); // render the page changes (via the render pipeline)
}
}

Understanding async JS with promises, task and job queue

I was looking into async behaviour in JS and it was going well for the most part. I understand the synchronous way of executing code, the single thread of JS and how callbacks such as the one inside setTimeout will be timed by the Web browser API, and later on added to the task queue.
The event loop will constantly check the call stack, and only when it is empty (all sync code has executed), it will take functions that have been queued in the task queue. Pushes them back to the call stack and they are executed.
This is pretty straight forward and is the reason why following code:
console.log('start');
setTimeout(() => console.log('timeout'), 0);
console.log('end');
Will output start, end, timeout.
Now when I started reading about promises, I understood that they have higher priority than regular async code such as timeout, interval, eventlistener and instead will get placed in the job queue/microtask queue. The event loop will first prioritize that queue and run all jobs until exhaustion, before moving on to the task queue.
This still makes sense and can be seen by running:
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
This outputs start, end, promise, timeout. Synchronous code executes, the then callback gets pushed to the stack from the microtask queue and executed, setTimeout callback task from the task queue gets pushed and executed. All good so far.
I can wrap my head around the example above where the promise gets resolved immediately and synchronously, as told by the official documentation. The same would happen if we were to create a promise with the new keyword and provide an executor function. That executor function will execute synchronously and resolve the function. So when then is encountered, it can just run asynchronously on the resolved promise.
console.log('start');
const p1 = new Promise(resolve => {
console.log('promise 1 log');
resolve('promise 1');
});
p1.then(msg => console.log(msg));
console.log('end');
The snippet above will output start, promise 1 log, end, promise 1 proving that the executor runs synchronously.
And this is where i get confused with promises, let's say we have the following code:
console.log('start');
const p1 = new Promise(resolve => {
console.log('promise 1 log');
setTimeout(() => {
resolve('promise 1');
}, 0);
});
p1.then(msg => console.log(msg));
console.log('end');
This will result in start, promise 1 log, end, promise 1. If the executor function gets executed right away, that means that the setTimeout within it will get put on the task queue for later execution. To my understanding, this means the promise is still pending right now. We get to the then method and the callback within it. This will be put in the job queue. the rest of the synchronous code is executed and we now have the empty call stack.
To my understanding, the promise callback will have the priority now but how can it execute with the still unresolved promised? The promise should only resolve after the setTimeout within it is executed, which still lies inside the task queue. I have heard, without any extra clarification that then will only run if the promise is resolved, and from my output i can see that's true, but i do not understand how that would work in this case. The only thing i can think of is an exception or something similar, and a task queue task getting the priority before the microtask.
This ended up being long so i appreciate anyone taking the time to read and answer this. I would love to understand the task queue, job queue and event loop better so do not hesitate posting a detailed answer! Thank you in advance.
We get to the then method and the callback within it. This will be put in the job queue.
No, calling then doesn't put anything in the job queue immediately if the promise is still pending. The callback will be installed on the promise for execution later when the promise is fulfilled, just like an event handler. Only when you call resolve(), it actually puts it in the job queue.
This works just like the setTimeout, where you wrote "[the] callback […] will be timed by the Web browser API, and later on added to the task queue" - it doesn't immediately queue a task that somehow waits, but it waits and then queues a task to execute the callback.
... the promise callback will have the priority now ...
Tasks in the microtask queue are given priority over those in the task queue only when they exist.
In the example :
No microtask is queued until after the setTimout() task has resolved the Promise.
The task and microtask are not in competition. They are sequential.
Delays imposed by the task queue and microtask queue (in that order) are additive.
... but how can it execute with the still unresolved promised?
It doesn't. The .then() callback will execute only after the promise is fulfilled, and that fulfillment is dependent on a task placed in the task queue by setTimeout() (even with a delay of zero).
JS engine has got 1 call stack, macro task queue, micro task queue and web api's. More about basic concept: https://stackoverflow.com/a/30910084/1779091
In case of Promise, code inside the promise will run and when resolve is called, then the callback gets added into the micro queue.
Whereas setTimeout runs in the web api and once it completes, it puts the callback into the macro queue.
console.log('start');
setTimeout(() => console.log('timeout'), 0);
console.log('end');
Print start
Call the setTimeout web api and pass the call back into it
At this point the setTimeout may or may not have already completed. Whenever timer is exhausted, the callback will be put into the macro queue
Print end
Nothing left to execute, so check if the queue has something. This will output the timeout.
console.log('start');
setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('end');
Print start
Call the setTimeout web api and pass the call back into it
At this point the setTimeout may or may not have already completed. Whenever timer is exhausted, the callback will be put into the macro queue
Promise is resolved which puts the callback (the thing after .then) into the micro task queue
Print end
Nothing left to execute, so check if the queue has something. Micro task queue has higher priority then macro task queue. So 1st it will take the callback from micro task into the call stack and print promise and then take the callback from the macro task queue into the call stack and print timeout.
console.log('start');
const p1 = new Promise(resolve => {
console.log('promise 1 log');
resolve('promise 1');
});
p1.then(msg => console.log(msg));
console.log('end');
Print start
Create the promise and assign it to p1
Run the p1 which prints promise 1 log, then resolve which puts the callback (the thing after .then) into the micro task queue
Print end
Nothing left to execute, so check if the queue has something. The callback from the Micro task is put into the stack and it will print promise 1
console.log('start');
const p1 = new Promise(resolve => {
console.log('promise 1 log');
setTimeout(() => {
resolve('promise 1');
}, 0);
});
p1.then(msg => console.log(msg));
console.log('end');
Print start
Create the promise and assign it to p1
Run the p1 which prints promise 1 log, then calls setTimeout which invokes the web api. At this point the setTimeout may or may not have already completed. Whenever timer is exhausted, the callback will be put into the macro queue
Print end
Nothing left to execute, so check if the queue has something. The callback from the Macro task is put into the stack and it will run the resolve which puts the callback (the thing after .then) into the Micro task queue.
Nothing left to execute, so check if the queue has something. The callback from the Micro task is put into the stack and it will print promise 1

What is the difference between microtask and macrotask in javascript? [duplicate]

I've just finished reading the Promises/A+ specification and stumbled upon the terms microtask and macrotask: see http://promisesaplus.com/#notes
I've never heard of these terms before, and now I'm curious what the difference could be?
I've already tried to find some information on the web, but all I've found is this post from the w3.org Archives (which does not explain the difference to me): http://lists.w3.org/Archives/Public/public-nextweb/2013Jul/0018.html
Additionally, I've found an npm module called "macrotask": https://www.npmjs.org/package/macrotask
Again, it is not clarified what the difference exactly is.
All I know is, that it has something to do with the event loop, as described in https://html.spec.whatwg.org/multipage/webappapis.html#task-queue
and https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint
I know I should theoretically be able to extract the differences myself, given this WHATWG specification. But I'm sure that others could benefit as well from a short explanation given by an expert.
One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification).
After this macrotask has finished, all available microtasks will be processed, namely within the same go-around cycle. While these microtasks are processed, they can queue even more microtasks, which will all be run one by one, until the microtask queue is exhausted.
What are the practical consequences of this?
If a microtask recursively queues other microtasks, it might take a long time until the next macrotask is processed. This means, you could end up with a blocked UI, or some finished I/O idling in your application.
However, at least concerning Node.js's process.nextTick function (which queues microtasks), there is an inbuilt protection against such blocking by means of process.maxTickDepth. This value is set to a default of 1000, cutting down further processing of microtasks after this limit is reached which allows the next macrotask to be processed)
So when to use what?
Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future).
Otherwise, stick to macrotasks.
Examples
macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering
microtasks: process.nextTick, Promises, queueMicrotask, MutationObserver
Basic concepts in spec:
An event loop has one or more task queues.(task queue is macrotask queue)
Each event loop has a microtask queue.
task queue = macrotask queue != microtask queue
a task may be pushed into macrotask queue,or microtask queue
when a task is pushed into a queue(micro/macro),we mean preparing work is finished,so the task can be executed now.
And the event loop process model is as follows:
when call stack is empty,do the steps-
select the oldest task(task A) in task queues
if task A is null(means task queues is empty),jump to step 6
set "currently running task" to "task A"
run "task A"(means run the callback function)
set "currently running task" to null,remove "task A"
perform microtask queue
(a).select the oldest task(task x) in microtask queue
(b).if task x is null(means microtask queues is empty),jump to step (g)
(c).set "currently running task" to "task x"
(d).run "task x"
(e).set "currently running task" to null,remove "task x"
(f).select next oldest task in microtask queue,jump to step(b)
(g).finish microtask queue;
jump to step 1.
a simplified process model is as follows:
run the oldest task in macrotask queue,then remove it.
run all available tasks in microtask queue,then remove them.
next round:run next task in macrotask queue(jump step 2)
something to remember:
when a task (in macrotask queue) is running,new events may be registered.So new tasks may be created.Below are two new created tasks:
promiseA.then()'s callback is a task
promiseA is resolved/rejected: the task will be pushed into microtask queue in current round of event loop.
promiseA is pending: the task will be pushed into microtask queue in the future round of event loop(may be next round)
setTimeout(callback,n)'s callback is a task,and will be pushed into macrotask queue,even n is 0;
task in microtask queue will be run in the current round,while task in macrotask queue has to wait for next round of event loop.
we all know callback of "click","scroll","ajax","setTimeout"... are tasks,however we should also remember js codes as a whole in script tag is a task(a macrotask) too.
I think we can't discuss event loop in separation from the stack, so:
JS has three "stacks":
standard stack for all synchronous calls (one function calls another, etc)
microtask queue (or job queue or microtask stack) for all async operations with higher priority (process.nextTick, Promises, Object.observe, MutationObserver)
macrotask queue (or event queue, task queue, macrotask queue) for all async operations with lower priority (setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering)
|=======|
| macro |
| [...] |
| |
|=======|
| micro |
| [...] |
| |
|=======|
| stack |
| [...] |
| |
|=======|
And event loop works this way:
execute everything from bottom to top from the stack, and ONLY when the stack is empty, check what is going on in queues above
check micro stack and execute everything there (if required) with help of stack, one micro-task after another until the microtask queue is empty or don't require any execution and ONLY then check the macro stack
check macro stack and execute everything there (if required) with help of the stack
Micro stack won't be touched if the stack isn't empty. The macro stack won't be touched if the micro stack isn't empty OR does not require any execution.
To sum up: microtask queue is almost the same as macrotask queue but those tasks (process.nextTick, Promises, Object.observe, MutationObserver) have higher priority than macrotasks.
Micro is like macro but with higher priority.
Here you have "ultimate" code for understanding everything.
console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);
const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
setTimeout(() => {
console.log('stack [4]')
setTimeout(() => console.log("macro [5]"), 0);
p.then(() => console.log('micro [6]'));
}, 0);
console.log("stack [7]");
});
console.log("macro [8]");
/* Result:
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]
macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different
stack [1]
macro [8]
stack [7], stack [7], stack [7]
macro [2]
macro [3]
stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]
macro [5], macro [5], macro [5]
more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/
Macro tasks include keyboard events, mouse events, timer events (setTimeout) , network events, Html parsing, changing Urletc. A macro task represents some discrete and independent work. micro task queue has higher priority so macro task will wait for all the micro tasks are executed first.
Microtasks, are smaller tasks that update the application state and should be executed before the browser continues with other assignments such as
re-rendering the UI. Microtasks include promise callbacks and DOM mutation changes. Microtasks enable us to execute certain actions before the UI is re-rendered, thereby avoiding unnecessary UI rendering that might show an inconsistent application state.
Separation of macro and microtask enables the
event loop to prioritize types of tasks; for example, giving priority to performance-sensitive tasks.
In a single loop iteration, one macro task at most is processed
(others are left waiting in the queue), whereas all microtasks are processed.
Both task queues are placed outside the event loop, to indicate that the act of adding tasks to their matching queues happens outside the
event loop. Otherwise, any events that occur while JavaScript code is
being executed would be ignored. The acts of detecting and adding
tasks are done separately from the event loop.
Both types of tasks are executed one at a time. When a task starts executing, it’s executed to its completion. Only the browser can stop
the execution of a task; for example, if the task takes up too much
time or memory.
All microtasks should be executed before the next rendering because their goal is to update the application state before rendering occurs.
The browser usually tries to render the page 60 times per second,
It's accepted that 60 frames per second are the rate at which
animations will appear smooth. if we want to achieve smooth-running
applications, a single task, and all microtasks generated by that task
should ideally complete within 16 ms. If a task gets executed for more
than a couple of seconds, the browser shows an “Unresponsive script”
message.
reference John Resig-secrets of JS Ninja
I created an event loop pseudocode following the 4 concepts:
setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering are part of the macrotask queue. One macrotask item will be processed first.
process.nextTick, Promises, Object.observe, MutationObserver are part of the microtasks queue. The event loop will process all of the items in that queue including once that were processed during the current iteration.
There is another queue called animation queue which holds animation changes task items which will be processed next. All tasks which exists in this queue will be processed (not including new one which were added during the current iteration). It will be called if it is time for rendering
The rendering pipeline will try to render 60 times a second (every 16 ms)
while (true){
// 1. Get one macrotask (oldest) task item
task = macroTaskQueue.pop();
execute(task);
// 2. Go and execute microtasks while they have items in their queue (including those which were added during this iteration)
while (microtaskQueue.hasTasks()){
const microTask = microtaskQueue.pop();
execute(microTask);
}
// 3. If 16ms have elapsed since last time this condition was true
if (isPaintTime()){
// 4. Go and execute animationTasks while they have items in their queue (not including those which were added during this iteration)
const animationTasks = animationQueue.getTasks();
for (task in animationTasks){
execute(task);
}
repaint(); // render the page changes (via the render pipeline)
}
}

event loop prefers microtask queue over callback queue?

I was testing out concepts of asynchronous code in JS . Got confused between callback queue & microtask queue order. Whenever promise objects gets resolved , the fulfillment method { then } is pushed into microtask queue while the callbacks of browser timer functions such as setTimeout is pushed into callback queue. Event loop continuously checks queue and pushes functions from queue into call stack whenever call stack gets empty. Event loop should prefer microtask queue over normal callback queue but in the example : https://jsfiddle.net/BHUPENDRA1011/2n89ftmp/ it's happening otherwise.
function display(data) {
console.log('hi back from fetch');
}
function printHello() {
console.log('hello');
}
function blockfor300ms() {
for (let i = 0; i < 300; i++) {
// just delaying logic
}
}
// this sets the printHello function in callback queue { event loop queue }
setTimeout(printHello, 0);
const futureData = fetch('https://api.github.com/users/xiaotian/repos');
// after promise is resolved display function gets added into other queue : Microtask queue { job queue}
futureData.then(display);
// event loop gives prefrence to Microtask queue ( untill its complete)
blockfor300ms();
// which runs first
console.log('Me first !')
expected output
Me first !
hi back from fetch
hello
actual output :
Me first !
hello
hi back from fetch
Kindly let me know how it's happening over here.
Thanks
While it is true, what "kib" stated:
"your function blockfor300ms doesn't block the thread long enough for
the fetch to receive a response"
sadly this is irrelevant, because even if you did block execution until after you received a response to your fetch call, you would still see the same result...
(see sample code snippet below, you can block execution with an alert box or a long loop of non-async XMLHttpRequest calls, I received the same result)
Unfortunately, fetch does not work as described by all the blogs and resources I've found... which state that
Fetch will add it's promise chain to the micro-tasks queue and run before any callbacks on the next tick of the event loop.
From the sample code below, it appears fetch does not simply add it's resolve handler function to the micro-tasks queue as described by others, because as Lewis stated since it requires network activity it is being handled by the Network Task Source. But, I don't believe this is due to printHello "blocking" the network task, because fetch is being fired prior to printHello in my sample code below, and the network response would occur before the timer completed as well.
As you can see in the example below I have the printHello delayed to be added to the Task Queue long after the fetch response has been received (2000ms), yet if we block the code execution for longer than 2000ms (so that there is still running execution context) then "Hello" will be printed first. Meaning the fetch resolve() handler is NOT being simply added to the Micro-Task Queue where it would have fired along with the other promise handlers.
So, why is it still being logged after the callback if the response is received and theoretically added to the Task Queue prior to the timer task completing (at 2000ms)? Well, my guess is that the timer task source must be receiving priority over that of the network task source. And therefore both are sitting in their task queue, but the timer task queue is firing before the network task queue...
Links to specs:
Timer Task Source - https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timer-task-source
Networking Task Source - https://html.spec.whatwg.org/multipage/webappapis.html#networking-task-source
function display(data){console.log("Fetch resolved!")}
function printHello(){console.log("Callback Time")}
function blockExecution() {
console.log("Blocking execution...");
alert('Wait at least 2000ms before closing');
}
const futureData = fetch('https://jsonplaceholder.typicode.com/todos/1');
futureData.then(response => response.json()).then(display);
setTimeout(printHello, 2000);
const p = new Promise(
// this is called the "executor"
(resolve, reject) => {
console.log("I'm making a promise...");
resolve("Promise resolved!");
console.log("Promise is made...");
}
);
p.then(
// this is called the success handler
result => console.log(result)
);
blockExecution();
console.log("Execution ended!");
futureData is actually a fetch promise so there is absolutely a network task queued into task queue when fetch is called. As the result, printHello will definitely be executed before the network task since they're both tasks. And method display will only be put into microtask queue when the promise of network task is resolved. Microtasks, by definition, are only executed at the end of each task. So display will be called at the end of the network task when printHello has already been called long time before.
If you want display to be called before printHello, futureData must only queue microtasks. Let's modify your example a bit.
function display(data) {
console.log('hi back from fetch');
}
function printHello() {
console.log('hello');
}
let now = Date.now();
function executeFutureDataWithMicrotasksOnly() {
// Execute microtasks continually in 300ms.
return Promise.resolve().then(() => Date.now() - now < 300 && executeFutureDataWithMicrotasksOnly());
}
function blockfor300ms() {
for (let i = 0; i < 300; i++) {
// just delaying logic
}
}
// this sets the printHello function in callback queue { event loop queue }
setTimeout(printHello, 0);
const futureData = executeFutureDataWithMicrotasksOnly();
// after promise is resolved display function gets added into other queue : Microtask queue { job queue}
futureData.then(display);
// event loop gives prefrence to Microtask queue ( untill its complete)
blockfor300ms();
// which runs first
console.log('Me first !')
As you can see from the above example, if you replace fetch with a method having only microtasks, the execution order is changed as expected though both fetch and executeFutureDataWithMicrotasksOnly are executed in a similar time interval. When futureData no longer queues tasks, all microtasks including display will be executed at the end of the currently executing task, which is the previous task of task printHello.
I think the problem comes from the fact that your function blockfor300ms doesn't block the thread long enough for the fetch to receive a response.
There won't be anything in the job queue (yet) when the event loop will see that it can call printHello.
Using Visualization
This will help you in giving a better understanding of how javascript works.
Here in this case fetch(Promise) is taking more time than setTimeout so when the event loop cycle is running fetch(Promise) was still in progress and setTimeout is executed first as it took less time and came out of the task queue and it gets processed and when fetch(Promise) ends it comes out of the microtask queue.
If you will increase setTimeout time then the first fetch(Promise) will occur first and then setTimeout will occur. Hope this solves your question.
It seems to be easier than we all think:
It is possible for a microtask to be moved to a regular task queue, if, during its initial execution, it spins the event loop. HTML Living Standard #queue a microtask
When the loop is in the process of selecting a task form the task queue, it can choose to execute tasks that were previously queued into the microtask queue and now are part of task queue:
In that case, the task chosen in the next step was originally a microtask, but it got moved as part of spinning the event loop. HTML Living Standard #event loop processing model
Code that spins the loop is anything that includes parallel operations:
In parallel:
Wait until the condition goal is met.
HTML Living Standard #spin-the-event-loop

Why are callbacks considered asynchronous if we need to wait

I've read Why do I have to use await for a method to run asynchronously. What if I don't want to wait for the method to finish before continuing? and Are callbacks always asynchronous? and am still trying to understand when a callback is actually asynchronous.
For example, doThat will need to wait for GET data before doing anything with it. And as the second link above states, javascript is single threaded.
doThis(doThat);
function doThis(callback) {
$.get('http://some_api/some_resource', function (data) {
callback(data);
});
};
function doThat(data) {
// Do something with data
};
The only truly async functionality I've seen is with promises and multiple promises where I can, for example, load other data while animations are wrapping up. I'd like help better understanding when traditional callbacks are actually asynchronous. Concrete examples help.
Starting with the definition:
Asynchrony, in computer programming, refers to the occurrence of events independently of the main program flow and ways to deal with such events. These may be "outside" events such as the arrival of signals, or actions instigated by a program that take place concurrently with program execution, without the program blocking to wait for results.
-- Davies, Alex (2012). Async in C# 5.0, via Wikipedia on Asynchrony (computer programming)
In case of JavaScript, it works like this (simplified): There is a queue of tasks waiting to be executed by the main (and only) thread. When you load a script, it is a task placed on this queue. A task runs until it exits, and no other task can interrupt it. However, a task can cause other tasks to be placed on the queue. When one task finishes, the next task on the queue starts. If the queue is empty, the first task that enters the queue afterwards gets immediately executed.
The main ways for tasks to enter the queue, besides being the main task of script being parsed and executed: triggering an event will place handlers registered for that event on the task queue, and reaching a time scheduled by setTimeout or setInterval will place the associated task on the task queue.
In JavaScript context, everything that executes within the same task ("main program flow") is said to be synchronous. Everything that is executed in a future task is called asynchronous. Another way to tell is - if the next statement executes before the callback, it is asynchronous; if the next statement executes after the callback, it is synchronous.
$.get(opts, callback) is an asynchronous call, because the task that executes the function callback will be placed on the task queue when triggered by the onreadystatechange event. If you have a statement following it, it will get executed first; it is only after that task finishes, that the task which entered the task queue because of an update to your AJAX call will have a chance to run.
In contrast, $.each(collection, callback) is a synchronous call, because callback will be directly called from the each function, without exiting the current task, and will not generate any additional tasks (not by itself anyway, callback of course can generate additional tasks). The next statement will have to wait until each finishes iterating over every element of collection.
Note that promises are simply wrappers over this mechanism. Everything you can do with promises, you can do without them, just the code will not be as pretty and legible.
I can, for example, load other data while animations are wrapping up.
This is exactly what you can do with your code. Note that you can run other code (and indeed the "running other code" is what confuses people) while waiting for doThat to execute. Exactly like your animation example:
function doThis(callback) {
$.get('http://some_api/some_resource', function (data) {
callback(data);
});
};
function doThat(function (data) {
// Do something with data
});
function doAnotherThing(function (data) {
// Do something with data
});
doThis(doThat);
// without waiting for doThat to run, you can IMMEDIATELY call:
doThis(doAnotherThing);
// without waiting for either doThat or doAnotherThing you
// can output some pretty animations:
setInterval(do_animation,100);
// and without waiting for any of the above you can do anything else:
document.body.innerHTML += 'hello';
Note that what confuses most people is that the document.body.innerHTML='hello' part runs BEFORE any of the code above it. Which means that the callbacks are asynchronous because they get executed when data arrives, not when the function is called.
Note that not all callbacks are asynchronous. For example, Array.prototype.forEach() is synchronous:
document.body.innerHTML += 'before<br>';
['hello','world'].forEach(function(x){
document.body.innerHTML += x;
})
document.body.innerHTML += '<br>after';
What determines weather a callback is asynchronous or synchronous is the function that calls it. See also: I know that callback function runs asynchronously, but why?

Categories

Resources