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.
Related
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
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
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
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
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.