JavaScript async callbacks - Promise and setTimeout [duplicate] - javascript

This question already has answers here:
Promise vs setTimeout
(6 answers)
What is the relationship between event loop and Promise [duplicate]
(2 answers)
Closed 1 year ago.
In the following code:
setTimeout(() => console.log("hello"), 0);
Promise.resolve('Success!')
.then(console.log)
What should happen in my understanding:
setTimeout is called => print hello directly added to callback queue as time is 0
Promise.resolve => print Success! added to callback queue
If I am not wrong, the callback queue is FIFO.
But the code output is:
Success!
hello
What is the explanation?

There are 2 separate queues for handling of the callbacks. A macro and a micro queue. setTimeout enqueues an item in the macro queue, while promise resolution - to the micro queue. The currently executing macro task(the main script itself in this case) is executed synchronously, line by line until it is finished. The moment it is finished, the loop executes everything queued in the microtask queue before continuing with the next item from the macro queue(which in your case is the console.log("hello") queued from the setTimeout).
Basically, the flow looks like this:
Script starts executing.
MacrotaskQueue: [], MicrotaskQueue: [].
setTimeout(() => console.log("hello"), 0); is encountered which leads to pushing a new item in the macrotask queue.
MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [].
Promise.resolve('Success!').then(console.log) is read. Promise resolves to Success! immediately and console.log callback gets enqueued to the microtask queue.
MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [console.log('Success!')].
The script finishes executing so it checks if there is something in the microtask queue before proceeding with the next task from the macro queue.
console.log('Success!') is pulled from the microtask queue and executed.
MacrotaskQueue: [console.log("hello")], MicrotaskQueue: [].
Script checks again if there is something else in the microtask queue. There is none, so it fetches the first available task from the macrotask queue and executes it, namely - console.log("hello").
MacrotaskQueue: [], MicrotaskQueue: [].
After the script finishes executing the console.log("hello"), it once again checks if there is anything in the microtask queue. It is empty, so it checks the macrotask queue. It is empty as well so everything queued is executed and the script finishes.
This is a simplified explanation, though, as it can get trickier. The microtask queue normally handles mainly promise callbacks, but you can enqueue code on it yourself. The newly added items in the microtask queue will still be executed before the next macrotask item. Also, microtasks can enqueue other microtasks, which can lead to an endless loop of processing microtasks.
Some useful reference resources:
The event loop
Microtasks
Using Microtasks

There are two different queues involved here: a Task queue and a Microtask queue.
Callback functions scheduled using setTimeout are added in the task queue whereas the callbacks scheduled using promises are added in the microtask queue or a job queue.
A microtask queue is processed:
after each callback as long as the call-stack is empty.
after each task.
Also note that if a microtask in a microtask queue queues another microtask, that will also be processed before processing anything in the task queue. In other words, microtask queue will be processed until its empty before processing the next task in the task queue.
The following code snippet shows an example:
setTimeout(() => console.log('hello'), 0);
Promise.resolve('first microtask')
.then(res => {
console.log(res);
return 'second microtask';
})
.then(console.log);
In your code, callback function of setTimeout is added to the task queue and the Promise.resolve queues a micro-task in a microtask queue. This queue is processed at the end of the script execution. That is why "success" is logged before "hello".
The following image shows a step-by-step execution of your code:
Resources for further reading:
Tasks, microtasks, queues and schedules
JavaScript job queue and microtasks

Even though the timeout is 0, the callback function will still be added to the web API (after being fetched from the call stack). Web APIs are threads that you can’t access; you can just make calls like Ajax, Timeout, and the DOM.
Promise.resolve schedules a microtask whereas setTimeout schedules a macrotask. Microtasks are executed before running the next macrotask.
So in your example, the
Promise.resolve('Success!').then(console.log);
will be executed before the setTimout since promises have better priority than the setTimeout callback function in the event loop stack.

Related

Does Promise add the call backfunction to microtask queue?

I read some files about the microtask and macrotask queue. I know that the callback function of a resolved promise is added to the microtask, same as the other sync tasks. But the following code:
fetch=() => new Promise ((resolve) => resolve ({m: 121}))
function x() {
let d = 3;
fetch().then((data) => {
d += data.m;
console.log("b", d);
});
console.log("a", d);
}
x();
gives a result of
a 3
b 124
IAs I thought, since fetch here fetches a "local" file and it takes no time, so the callback function is immediately added to the microtask queue, and then console.log("a", d); is added to the microtask queue. If so, the result should be
b 124
a 124
So what is the reason for the real result? Why the call back function is executed after console.log("a", d);?
Whether or not a callback is added to the microtask queue or the callback queue has nothing to do with how long it takes to run. The Promise callbacks are always added to the microtask queue. Regardless of how long it takes. And, the queue is cleared, after the call stack is empty. In your example, the function x has to complete execution before your Promise callback can run. So, a is logged first.
Read more about the event loop here. But in a nutshell, browser API callbacks go in the callback queue, and higher priority callbacks like the Promise callbacks go in the microtask queue. The queues are cleared only after the call stack is empty.
This can lead to puzzling behaviors like the following code running forever since the call stack never becomes empty.
let flag = true;
setTimeout(() => flag = false, 200)
while (flag) console.log('hello')

What does the callstack look like with promises and async/await?

I recently watched this video: https://www.youtube.com/watch?v=8aGhZQkoFbQ and am trying to find a similar explanation for with Promises. Specifically in node.
I'm probably wrong but I'm under the impression that Promises are placed on a microtask queue, the microtask queue is only processed when the callstack is empty and once the event-loop starts processing the microtask queue, it processes it until the queue is empty.
const sleep = (ms) => new Promise((res) => {
setTimeout(res, ms)
});
const doTask1 = (workerId) => sleep(1000).then(() => console.log(`[${workerId}] Completed Task 1`));
const doTask2 = (workerId) => sleep(5000).then(() => console.log(`[${workerId}] Completed Task 2`));
const worker = async (workerId) => {
console.log(`[${workerId}] Starting work`)
await doTask1(workerId);
await doTask2(workerId);
console.log(`[${workerId}] Completed all tasks`);
};
worker('A');
worker('B');
When the snippet above is run, both worker A and B will start Task 1 and 2 at around the same time and complete at around the same time. I'm having trouble consolidating that behavior with what I've read/seen about Promises/microtasks and event-loops.
I imagine that when the code executes, the call stack looks like:
Callstack: [main.js]
Callstack: [main.js][worker('A')]
Callstack: [main.js][worker('A')][console.log()]
Callstack: [main.js][worker('A')][await doTask1()]
When we use await, what happens to the callstack? I suppose this is related to how generators work?
Callstack: ?? | Microtask Queue: [doTask1()]
What does it mean for the event-loop to "process" the microtask queue? Does processing doTask1() mean the event-loop is waiting 1 second? That doesn't sound right. Is the 1 second wait being done on a different thread (is that the right term?) as shown in the Youtube video's depiction of WebAPIs? Once the timeout is complete, the Promise's res goes onto the message queue where the event loop eventually picks it up and resolves the Promise? Does the callstack have to be empty for the callstack to execute res?
I'm stuck at this point and not sure what happens next in the callstack, the message queue, the microtask queue and how the event-loop decides what to do next.

How to observe multiple task queues in JS event loop [duplicate]

This question already has answers here:
Which types of queues are in event loop?
(3 answers)
Closed 2 years ago.
I'm reading an article that explains the event loop and execution timings. In it, it's mention that there can be multiple task queues that the event loop can choose to from to execute tasks. My main question is when does the browser decide to create a new queue? I've tried to observe this happening, but so far haven't been able to.
Contrasted with this other article on the subject, there's no mention of multiple queues, so I'm either misunderstanding something or one of the two articles is incorrect somewhere.
I believe the two articles in question are just using different terminology.
In article 1:
An event loop has multiple task sources which guarantees execution order within that source...
In article 2
the event loop can have multiple task queues... tasks must be processed in insertion order in every queue.
To answer my own question about observing different queues, I think it's as simple as this:
function foo() {
console.log('Start of queue');
bar();
Promise.resolve().then(function() {
console.log('Promise resolved');
});
console.log('End of queue');
}
function bar() {
setTimeout(function() {
console.log('Start of next queue');
console.log('End of next queue');
}, 0);
}
foo();
//-> Start of queue
//-> End of queue
//-> Promise resolved
//-> Start of next queue
//-> End of next queue
The first task queue (or task source) is foo(). foo() calls bar(), and bar() calls a setTimeout() which sets up a new task queue (or task source). The way we can observe each task queue is to resolve a Promise. The Promise callback is inserted into the micro queue. All micro queue tasks are executed at the end of every task queue (or task source). Because we see Promise resolved between the End of queue and Start of next queue console logs, we can conclude that that we're observing different event queues.
https://www.youtube.com/watch?v=u1kqx6AenYw&feature=youtu.be This will help you understand I think. Skip to the 7min mark.
There can be multiple queues in the task queue, and then also micro task queue.
In HTML terms, the event loop for a page or set of pages from the same domain can have multiple task queues. Tasks from the same task source always go into the same queue, with the browser choosing which task queue to use next.

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.

Difference between microtask and macrotask within an event loop context

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)
}
}

Categories