Node.js event queues, Promises, and setTimeout() -- in what sequence? - javascript

SYNOPSIS:
In Node.js event queues, and code like "new Promise((r) => setTimeout(r, t));", is the setTimeout() evaluated NOW, in the microqueue for Promise resolves, or where?
DETAILS:
I'm reading through Distributed Systems with Node.js (Thomas Hunter II, O'Reilly, 3rd release of First Edition). It tells me that Node.js goes thru each queue in turn:
Poll: for most things, including I/O callbacks
Check: for setImmediate callbacks
Close: when closing connections
Timers: when setTimeout and setInterval resolve
Pending: special system events
There are also two microqueues evaluated after each queue is empty, one for promises and one for nextTick().
On the book's p.13 he has an example where an await calls a function that returns "new Promise((r) => setTimeout(r, t));". The book code is:
const sleep_st = (t) => new Promise((r) => setTimeout(r, t));
const sleep_im = () => new Promise((r) => setImmediate(r));
(async () => {
setImmediate(() => console.log(1));
console.log(2);
await sleep_st(0);
setImmediate(() => console.log(3));
console.log(4);
That is,
setImmediate(() => console.log(1));
console.log(2);
Promise.resolve().then(() => setTimeout(() => {
setImmediate(() => console.log(3));
console.log(4);
This is what I think is going on:
The program starts with a task in the Poll queue, the p.13 code. It starts running.
The Check queue gets a task and the "2" printed to the console.
The "await sleep_st(0)" will have called setTimeout, which puts a
task on the Timer queue. Since the timeout is zero, by the time we
access the Timer queue there will be work to do. The sleep_st(0)
returns a Promise.
This ends the work of the Poll queue.
Now the result micro queue starts. My code resumes executing. This should start with
setImmediate() and console.log(4).
This means that the output to the console is "2 4". However, the book says the proper sequence is "2 1 4 3". That is, the event queue for Check, and perhaps Timer, gets involved.
What, then, happens in the promise result microqueue?

In Node.js event queues, and code like "new Promise((r) => setTimeout(r, t));", is the setTimeout() evaluated NOW, in the microqueue for Promise resolves, or where?
The call to setTimeout is evaluated "now." (The setTimeout callback is called later as appropriate, during the time phrase.) When you do new Promise(fn), the Promise constructor calls fn immediately and synchronously, during your call to new Promise(fn). This is so the function (called an executor function) can start the asynchronous work that the promise will report on, as your two examples (one starts the work by calling setTimeout, the other by calling setImmediate.)
You can easily see this with logging:
console.log("Before");
new Promise((resolve, reject) => {
console.log("During");
setTimeout(() => {
console.log("(fulfilling)");
resolve();
}, 10);
})
.then(
() => {
console.log("On fulfillment");
},
() => {
console.log("On rejection");
}
);
console.log("After");
That logs
Before
During
After
(fulfilling)
On fulfillment
because
It calls console.log("Before"); before doing anything else.
It calls new Promise and log console.log("During"); synchronously in the callback.
It calls console.log("After"); after creating the promise and adding fulfillment and rejection handlers to it.
It calls console.log("(fulfilling)"); when the timer fires and fulfill the promise.
It calls console.log("On fulfillment"); when the fulfillment handler is called.
On your notes on the sequence:
The "await sleep_st(0)" will have called setTimeout
Just to be really clear, it's specifically the sleep_st(0) part that called setTimeout. All await did was wait for the promise sleep_st returned after calling setTimeout to settle.
You may find this example useful, see inline comments:
const sleep = ms => new Promise(resolve => {
// Happens immediately and synchronously when `sleep` is called
console.log("calling setTimeout");
setTimeout(() => {
// Happens later, during the timer phase
console.log("fulfilling promise");
resolve(); // <=== If there are any attached promise handlers,
// this queues calls to them in the microtask
// queue, to be done after this "macro" task
// running in the timer phase is complete
}, ms);
});
const example = async (label) => {
// Happens synchronously and immediately when `example` is called
await sleep(0);
// Happens in a microtask queued by the fulfillment of the promis
console.log(`Sleep done: ${label}`);
};
(async () => {
await Promise.all([example("a"), example("b")]);
// Happens in a microtask queued by fulfillment of the `Promise.all`
// promise
console.log("All done");
})();
The output is:
calling setTimeout
calling setTimeout
fulfilling promise
Sleep done: a
fulfilling promise
Sleep done: b
All done
Note how the code for Sleep done: a was executed between the two tasks for the timer callbacks, because those timer callbacks are "macro" tasks and promise fulfillment callbacks are queued as microtask to be run at the end of the current macrotask.

by the time we access the Timer queue there will be work to do
Before get to timer phase, there is check phase, so "1" is printed before "3",
but i exectued the code on my window, the result is 2 4 1 3, that is setTimeout and setImmediate will race for exectued, sometime setTimeout first, sometile second, i executed the example code in this book for many time in a short time

Related

Why is the execution order not sequentyla in promises and await functions in JavaScript?

I have a code:
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log(await promise1);
console.log("sync call");
}
hh();
result of this code is:
Second promise
First promise
sync call
My first question why sync call is not on the first place?
Then I just deleted from the code console.log(await promise1);
So the code looks like this:
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log("sync call");
}
hh();
End result now is:
sync call
Second promise
So now sync call is on the first place, just beacuse I deleted the second call, why?
This is what happens in that code:
First version
promise1 and promise2 are promises in status fulfilled -- you could have used the Promise.resolve function instead of new Promise.
promise2.then() registers a callback to be executed when the promise is fulfilled, which it is, so the callback is enqueued as a job in the promise job queue.
await promise1 suspends the hh function and registers hh to be resumed when the promise is fulfilled, which it is, so the resume of hh is enqueued as a job in the promise job queue (which now has two entries).
hh() returns a promise (that is ignored) and the JS call stack becomes empty. Now the event loop will extract the first job and execute it. This outputs "Second promise".
Then the call stack is empty again. The event loop will extract the last job and execute it. This resumes the hh execution context, which executes the console.log and outputs "First promise". It then continues with "sync call". Note that these two console.log are executed in one synchronous sequence, so there is absolutely no way that they could have their outputs in a different relative order.
Second version
Here the await is missing, so there is no suspension of the hh execution context, and the promise job queue only has one entry.
As hh will run to completion the output "sync call" is the first output -- no job got a chance to run yet.
When hh() returns the JS event loop will pick up that one job and executes it. This is the callback that outputs "Second promise".
For the first question:
since promise1 and promise2 are resolved as soon as they are initialized, they will be in `resolved' state
in your code you call promise2.then(a=> console.log(a)) i.e. a=> console.log(a) will be pushed to a (asynchronous) execution task (asynchronous by fulfilled) later when this string function is complete
but now all hh functions are waiting for promise1<fulfilled> to finish so the default console.log(await promise1) and console.log('sync call') will become default synchronous functions with promise1
ending hh
and the function hh ends the synchronous execution continue to execute the waiting tasks now because the promise2 execution task added before it will be completely separated from the waiting task of hh it will be javascript executes because it is prepended resulting in the first function a => console.log(a) being executed
finish the task of promise2 continue to promise1 it works as synchronous code
Regarding the 2nd question
as I explained earlier a => console.log(a) is added to the task (asynchronously ready run by fulfilled) and hh does not depend on it leading to the next synchronous function continue executing console.log('sync call') then hh completes it continues executing promise2 task
then you call await promise1 it will also put the whole block of code afterwards (like you called .then above) into a task that executes (asynchronously)
This is achieved because javascript is a single-threaded language:
https://dev.to/bbarbour/if-javascript-is-single-threaded-how-is-it-asynchronous-56gd#:~:text=Javascript%20is%20a%20single%20threaded,times%20that%20can%20be%20harmful.
you can also achieve the above by using asynchronous functions like setTimeout...
More information at: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
Your promises are already resolved when your code reaches the last three operations. So, .then() is being called for promise2 and that schedules it to be picked up by the event loop once the current function is executed and this will happen under normal circumstances, this is why your second example first displays "sync call" and only then "Second Promise".
Yet, your await ensures that the current function is paused which allows the event loop to pick up the .then() of promise2 and evaluate the results of promise1. If you invert the order of your first and second line of the last three, then you get "First promise", "Second promise" and "sync call" being displayed.
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
console.log(await promise1);
promise2.then((a)=>console.log(a));
console.log("sync call");
}
hh();
A simple conversion of your initial code block into code that doesn't use async/await probably shows why console.log("sync call") isn't executed first.
async function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((a)=>console.log(a));
console.log(await promise1);
console.log("sync call");
}
hh();
function hh(){
const promise1 = new Promise((resolve,reject) => {
resolve("First promise");
});
const promise2 = new Promise((resolve,reject) => {
resolve("Second promise");
});
promise2.then((value) => console.log(value));
return promise1.then((value) => {
console.log(value);
console.log("sync call");
});
}
hh();
The above should give you a clear picture of why console.log("sync call") is not logged first.
Everything after an await becomes part of a then() callback, since the function is paused until the promise resolves. Statements are executed in sequential order, so console.log("sync call") can only be executed after console.log(await promise1).
console.log(a);
console.log(await promiseB);
console.log(c);
console.log(d);
console.log(await promiseE);
console.log(f);
console.log(await promiseG);
Can be written as:
console.log(a);
promiseB.then((b) => {
console.log(b);
console.log(c);
console.log(d);
return promiseE;
}).then((e) => {
console.log(e);
console.log(f);
return promiseG;
}).then((g) => {
console.log(g);
});
FIRST QUESTION ANSWER : You have suspended the Program's Execution by using the following line --> console.log(await promise1) until promise1 either gets resolved or rejected. During this suspended phase, the JS callstack is empty and the promise2 was already resolved and moved to the micro queue. Now, here comes the event loop. Event Loop sees that since the Callstack is empty, I should put whatever is present in my micro queue to the JS callstack and puts promise2's result (Second promise) to the call stack and it gets printed first and JS call stack becomes empty. Now,as you have suspended the execution of the code until promise1 resolves, First promise gets moved to the micro queue and again since the call stack is empty, Event Loop places the promise1's result on the call stack and First promise gets printed. After this, JS Single Thread moves to the next line and you see the sync call as your last printed statement
SECOND QUESTION ANSWER: The line promise2.then((a)=>console.log(a)); is asynchronus in nature, JS Single thread moves to the next line and push this line (console.log("sync call");) to the JS callstack. During this process, promise2 gets resolved and is moved to the Micro Queue. BUT Event Loop waits untill JS Call Stack is empty and will only push the promise2's result on the Call Stack, when console.log("sync call"); get executed and poppes out of the call stack. That's why you see sync call first and then,Second promise.

Why does Promise hold the execution of the Call Stack? [duplicate]

This question already has answers here:
Correct way to write a non-blocking function in Node.js
(2 answers)
Closed 8 months ago.
function main() {
console.log("S-1");
setTimeout(() => {
console.log("setTimeout");
}, 0);
new Promise((resolve, reject) => {
for (let i = 0; i < 10000000000; i++) {}
resolve("Promise");
}).then((res) => console.log(res));
console.log("S-2");
}
// Invoke function
main();
When I run the main() function, the output I get is:
'S-1'
'S-2'
'Promise'
'setTimeout'
After 'S-1' is logged, there is a big time gap, and then after few seconds, the rest gets logged, at once.
Shouldn't the time gap occur after logging 'S-2'?
The function inside the Promise constructor runs synchronously, on the same (and only) main thread as the rest of the script. Constructing a Promise doesn't result in a separate thread being created.
If you do some heavy processing immediately inside a Promise constructor, that heavy processing will have to complete before control flow is yielded back to the outside of the Promise constructor. In this case, it means that the many iterations of the loop must finish before:
the constructor finishes completely
The constructor resolves to a Promise
That Promise gets a .then handler attached to it
Finally, the console.log("S-2"); line runs
To get the output you're desiring or expecting without moving the logs around, you'd have to offload the expensive code to a different environment, such as to a worker or a server (using a network request).
As #CertainPerformance had said, Javascript can only handle one call-stack. It needs to finish this thread once and for all. For promises and timeouts, it uses the job and task queues. The event loop prioritizes the job queue and then the task queue.
Lets try to understand what you wrote in terms of call-stack and queues.
function main() {
// 1. execute straight forward
console.log("S-1");
// 2. sets / schedules a timer, the anonymous function is then stored in the callback
setTimeout(() => {
console.log("setTimeout");
}, 0);
// 3. create a new Promise, with the constructor acting as executor
// executes immediately, as its designed to set the state of the Promise for the then() to react to.
new Promise((resolve, reject) => {
for (let i = 0; i < 10000000000; i++) {}
// 3.5 the resolve signals that the execution is finished
// either returns the promise or passes through the then()
resolve("Promise");
// 4. after the promise execution has been resolved,
// then() creates another anonymous function, and stores in the callback.
// at this point there are 2 entries in the callback
}).then((res) => console.log(res));
// 5. once the above Promise executor has been executed, execute this
console.log("S-2");
// By now, since both the timer and promise execution has been done,
// the timeout is put into the Task Queue and the Promise then() is put into the Job Queue.
}
// 0. puts this into the call stack and execute
main();
// 6. The event loop deques all of the call stack from the job queue and puts it into the main call stack to execute.
// hence, the then() is executed first
// 7. The event loop then deques all of the call stack from the task queue and puts it into the main call stack to execute.
// hence, the timeout function is executed next.
If you want to make it a callback.
function main() {
console.log("S-1");
setTimeout(() => {
console.log("setTimeout");
}, 0);
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("promise");
}, 1000);
}).then((res) => console.log(res));
console.log("S-2");
}
// Invoke function
main();
Reference: https://medium.com/#idineshgarg/let-us-consider-an-example-a58bb1c11f55

Macro task vs micro task

why the event loop prioritizes cartoon's setTimeouts than the then's setTimeout? what I know is, the event loop should prioritize the micro task such as promises, then executes the message queue(cartoon's setTimeouts).
const tom = () => console.log('Tom');
const jerry = () => console.log('Jerry');
const doggy = () => console.log('Doggy');
const cartoon = () => {
console.log('Cartoon');
setTimeout(tom, 0);
setTimeout(doggy, 0);
new Promise((resolve, reject) => {
resolve('I am a Promise, right after tom and doggy! Really?');
}).then(resolve => {
console.log(resolve);
setTimeout(() => {
console.log('inner timeout')
}, 0)
});
new Promise((resolve, reject) =>
resolve('I am a Promise after Promise!')
).then(resolve => console.log(resolve));
jerry();
}
cartoon();
Output:
"Cartoon"
"Jerry"
"I am a Promise, right after tom and doggy! Really?"
"I am a Promise after Promise!"
"Tom"
"Doggy"
"inner timeout"
Caveat: Don't make assumptions about promise timing (other than that fulfillment/rejection handlers will not be called synchronously, which isn't an assumption, it's a guarantee genuine promises provide). It's fine to do this sort of thing to explore what happens, as a learning exercise, but don't make code rely on the interaction between tasks and microtasks. :-)
why the event loop prioritizes cartoon's setTimeouts than the then's setTimeout?
Because they were scheduled earlier.
what I know is, the event loop should prioritize the micro task such as promises, then executes the message queue(cartoon's setTimeouts).
Which is consistent with the results you're getting. The two calls to setTimeout at the top level of cartoon happen immediately. The call to setTimeout from the promise fulfillment handler doesn't happen until after those have already run. It's when they're scheduled that sets the order in which they're later executed (since they all have the same timeout value). (It doesn't matter that the setTimeout is in a fulfillment handler, even if you called setTimeout where you created the promise, you'd still be calling it after the other two, so it would be scheduled for after the other two.)
(Side note: Promise.resolve(x) is equivalent to new Promise(resolve => resolve(x)) and easier to type. :-) )
See further explanation in comments:
const tom = () => console.log('Tom');
const jerry = () => console.log('Jerry');
const doggy = () => console.log('Doggy');
const cartoon = () => {
console.log('Cartoon');
// ↓ Schedules the call to `tom`
setTimeout(tom, 0);
// ↓ Schedules the call to `doggy`; it was scheduled later with the same
// ↓ timeout value, so it will happen after the call to `tom`
setTimeout(doggy, 0);
// ↓ Creates a new promise which is immediately fulfilled
new Promise((resolve, reject) => {
resolve('I am a Promise, right after tom and doggy! Really?');
})
// ↓ Attaches a fulfillment handler to that promise
.then(resolve => {
// This handler won't be called until the microtasks queued by the
// task in which the promise was fulfilled are run
console.log(resolve);
// ↓ Schedules the call showing 'inner timeout', which will occur
// ↓ after the calls to `tom` and `doggy` because it was scheduled
// ↓ later (it would be even if it weren't in a promise fulfillment
// ↓ handler, just like `doggy` is called after `tom`)
setTimeout(() => {
console.log('inner timeout')
}, 0)
});
// ↓ Creates a new promise which is immediately fulfilled
new Promise((resolve, reject) =>
resolve('I am a Promise after Promise!')
)
// ↓ Attaches a fulfillment handler to that promise
.then(resolve => {
// This handler won't be called until the microtasks queued by the
// task in which the promise was fulfilled are run
console.log(resolve);
});
// ↓ Calls `jerry` right away, before any microtasks or other tasks scheduled by
// this task could possibly run
jerry();
}
cartoon();
To see task vs. microtask scheduling in action, here's an example (but see the caveat above):
const task = () => console.log("task");
const microtask1 = () => console.log("microtask1");
const fulfillment1 = () => console.log("fulfillment1 (microtask2)");
const microtask3 = () => console.log("microtask3");
const fulfillment2 = () => console.log("fulfillment2 (microtask4)");
setTimeout(task, 0);
queueMicrotask(microtask1);
Promise.resolve("hi")
.then(fulfillment1)
.then(fulfillment2);
queueMicrotask(microtask3);
// Output:
//
// microtask1
// fulfillment1 (microtask2)
// microtask3
// fulfillment2 (microtask4)
// task
When the JavaScript engine is given that code, it:
Runs the task to execute that code
Schedules task for "as soon as possible" in the task queue(some handwaving here, but that's accurate for the above; setTimeout(fn, 0) doesn't always schedule for ASAP, though, if nested a brief delay may be added)
Queues a microtask (microtask1)
Queues another microtask (fulfillment1) (because then` was called on a fulfilled promise)
Queues a third microtask (microtask3)
Reaches the end of that task, and processes the microtask queue
Runs microtask1
Runs fulfillment1
Doing that fulfills the promise created by the first call to then, which queues fulfillment2 as a microtask
Runs microtask3
Runs fulfillment2 because it was queued by #2.2 above
Reachs the end of the microtask queue
Runs the next available task
Calls task
Even though task was scheduled before any of the microtasks, it ran after them, even the one that wasn't scheduled until during the processing of the microtask queue.
But again, while useful for learning purposes, remember that the exact sequence above relies on exactly when the promises are fulfilled. In real world code, you have a promise because you have an asynchronous operation that may take N time, so you don't know when it will be fulfilled, so you can't make assumptions about when its reactions will be run.
When you schedule a micro-task that contains the setTimeout call, that micro-task will schedule another macro-task which will be added at the end of the macro-task queue.
As queue is FIFO (first in first out), tasks scheduled earlier are processed before the task scheduled using the micro-task.
In your case, tasks scheduled using the following setTimeout calls
setTimeout(tom, 0);
setTimeout(doggy, 0);
were scheduled earlier, so they are processed before the task scheduled using the micro-task.
what I know is, the event loop should prioritize the micro task such
as promises, then executes the message queue(cartoon's setTimeouts).
it is prioritized.
A microtask queue is processed:
after each callback as long as the call-stack is empty
after each task
So after your script has ended, micro-task queue will be processed until all the micro-tasks have been processed. In your case, first micro-task will schedule a macro-task in the macro-task queue.
At this point, macro-task and the micro-tasks look like this:
macro-tasks = [
() => console.log('Tom'),
() => console.log('Doggy'),
() => console.log('inner timeout')
]
micro-tasks = []
and the console output up to this point is:
Cartoon
Jerry
I am a Promise, right after tom and doggy! Really?
I am a Promise after Promise!
After micro-task queue has bee processed, next macro-task from the macro-task queue will be processed which is
() => console.log('Tom')
and the task scheduled by the micro-task
() => console.log('inner timeout')`
will be processed at the end.

Why does a promise inside setTimeout resolve before a subsequent setTimeout function?

Run the following code in browser or node.js:
setTimeout(() => {
console.log(1);
Promise.resolve().then(() => {
console.log(2);
});
});
setTimeout(() => {
console.log(3);
});
Promise callbacks and timer callbacks are scheduled differently from each other.
Promise handler functions are called asynchronously at the end of the task that scheduled them. You're scheduling two tasks (the timer callbacks). In the first task, since the promise is already fulfilled, the callback to the fulfillment handler happens asynchronously at the end of the task (it's a so-called microtask). Then the next task (the next timer callback) happens.
So your code executes like this:
The task for executing your script runs:
It schedules the timer callback for the first setTiemout.
It schedules the timer callback for the second setTiemout.
The task ends
The task for the first timer callback runs:
It does console.log(1)
It creates a fulfilled promise
It attaches a fulfillment handler to that promise
Since the promise is already fulfilled, this schedules a microtask to call the handler
The task ends
The microtask queued by the task runs, doing console.log(2)
The task for the second timer callback runs:
And logs console.log(3)
For browsers, you can find the details of this process in the Event Loops section of the specification.
It's worth noting that microtasks scheduled during a microtask run in the same "clear out the microtask queue" step as the microtask that scheduled them, before the next (macro)task runs. Consequently, the following gives us 1 2a 2b 3 rather than 1 2a 3 2b as you might otherwise expect:
setTimeout(() => {
console.log(1);
Promise.resolve()
.then(() => {
console.log("2a");
})
.then(() => {
console.log("2b");
});
}, 0);
setTimeout(() => {
console.log(3);
}, 0);
as does this (because it's largely the same thing):
setTimeout(() => {
console.log(1);
Promise.resolve()
.then(() => {
console.log("2a");
Promise.resolve()
.then(() => {
console.log("2b");
});
});
}, 0);
setTimeout(() => {
console.log(3);
}, 0);
UPDATE WITH DIAGRAMS :
Yes, that's because Promise.resolve() has a different queue called the JOB QUEUE or MICROTASK QUEUE, and this Job Queue has the higher Priority than the Callbacks Queue. Note that we are dealing with Promises now and not the callbacks when we do Promise.Resolve ! So Javascript ES6 came up with this Job Queue to handle Promises differently and call backs differently :)
So, Event Loop is going to check the Job Queue first and make sure there is nothing in that Queue before it starts looking, at the Call back Queue. So that the Job Queue has higher Priority than the call back queue.
I want to give an example to make it more clear because I feel that will make it explain more clearly.
setTimeout(()=>console.log("This is line 1"));
Promise.resolve("Two").then(data=>console.log("I am ",data));
setTimeout(()=>console.log("I am third"));
In the above code snippet, Promise is resolved first and only then the other setTimeout. That's because of the

Execution order in Promise()

Program - 1
new Promise(resolve => {
resolve('1');
Promise.resolve().then(() => console.log('2'));
}).then(data => {
console.log(data);
}); // output 2 1
Program -2
new Promise(resolve => {
Promise.resolve().then(() => {
resolve('1');
Promise.resolve().then(() => console.log('2'));
});
}).then(data => {
console.log(data);
}); // output 1 2
I am really confused with the output of both the programs. Can anyone please tell me how the execution thread works here?
What is hard to understand is that resolve doesn't work the same way as return. Creating Promises actually creates a new async context. Everything inside the first function of the Promise is executed synchronously. Then the .then() methods are called with the values resolved synchronously or asynchronously.
In the first example, you resolve '1', but then you create a new Promise with Promise.resolve() and you call the .then() right away. Because the new Promise is inside the first one, everything is called synchronously.
new Promise(resolve => {
resolve('1'); // Will be accessed in the .then() after then end of the callback
Promise.resolve().then(() => log('2')); // called before the end of the function
// because the context is not yet created
}).then(data => {
log(data);
}); // output 2 1
The second example is way harder. You actually create a Promise inside the first Promise and call its resolve right away. The order of execution will be:
first everything in the initial callbacks
After that, create sort of eventListeners from the resolve to the .then
When a resolve is called, execute the callback in .then
There is nothing in the initial function of the nested promise. So it goes straight to the then. The .then() calls the initial resolve(). Here, the kind of eventListener is created so we jump to the initial callback. Then we go back and execute the rest of the function.
new Promise(resolve => {
// Because we call resolve right away, the context of the original promise
// is created
Promise.resolve().then(() => {
resolve('1'); // Calling resolve jumps us to the .then
Promise.resolve().then(() => log('2')); // We execute after.then
});
}).then(data => {
log(data);
}); // output 1 2
If you removed the .resolve(), it would work like the first one:
new Promise(resolve => {
new Promise(() => {
resolve('1');
Promise.resolve().then(() => log('2'));
});
}).then(data => {
log(data);
}); // output 2 1
The difference lies in the context that the event loop is in when resolving the promise (resolve(1)).
There are 2 queues that execute code in JS:
microtask Q
macrotask Q
JS executes a microtask and then runs all the tasks from the macrotask Q (if any)
When resolving a promise from the executor (Promise(...)) you are running inside the macrotask Q. Code executed from inside a promise callback is executed on the microtask Q.
The difference that matters in this case is that when running from inside the microtask Q if you add other microtask (promise callbacks are microtasks) they get added to the current Q and get processed during this queue run.
This is what happens in case no 2, you are resolving the promise from inside a microtask Q, this ends up resolving the top level Promise and add the .then(data => { log(data); }); to the current microtask Q. So the callback will get executed in this run. On the other hand, the handlers of the nested promise Promise.resolve() is not executed now, as handlers are always called async.
Note: it is possible to add microtasks ad infinitum from inside the microtask Q, blocking the execution.

Categories

Resources