I have this ultra minimal Node.js server:
http.createServer(function (req, res) {
var path = url.parse(req.url).pathname;
if (path === "/") {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('Hello World');
res.end('\n');
} else {
if (!handler.handle(path, req, res)) {
res.writeHead(404);
res.write("404");
res.end();
}
}
}).listen(port);
After doing some benchmarks I saw considerable degradation of performances under high concurrency (> 10000 concurrent connections). I started to dig deeper on Node.js concurrency and the more I search, the more I am confused...
I created a minimal example, out of the http paradigm in order to try understand things a bit better:
function sleep(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
var t0 = performance.now();
async function init() {
await Promise.all([sleep(1000), sleep(1000), sleep(1000)]);
var t1 = performance.now();
console.log("Execution took " + (t1 - t0) + " milliseconds.")
}
init()
// Execution took 1000.299999985145 milliseconds.
From what I understand, Javascript operates on a single thread. That being said, I can't wrap my head around it acting like this:
| 1 second |
Thread #One >>>>>>>>>>>>>>
Thread #One >>>>>>>>>>>>>>
Thread #One >>>>>>>>>>>>>>
... obviously this doesn't makes sense.
So, is this a terminology problem around thread Vs worker with something like this:
| 1 second |
Thread #One (spawns 3 workers)
Worker #One >>>>>>>>>>>>>>
Worker #Two >>>>>>>>>>>>>>
Worker #Three >>>>>>>>>>>>>>
???
How is Node.js single-threaded but able to process three functions in parallel ?? If I am right with the parallel workers, is http spawning multiple workers for each incoming connections ?
A thread in a JavaScript program works by servicing an task (event) / job queue. Conceptually, it's a loop: Pick up a job from the queue, run that job to completion, pick up the next job, run that job to completion.
With that in mind, your example with promises works like this:
Running Node.js with your input file parses the code and queues a job to run the top-level code in the script.
The main thread picks up that job and runs your top-level code, which:
Creates some functions
Does var t0 = performance.now();
Calls sleep(1000), which
Creates a promise
Sets a timer to do a callback in roughly 1000ms
Returns the promise
Calls sleep(1000) twice more. Now there are three promises, and three timer callbacks scheduled for roughly the same time.
Awaits Promise.all on those promises. This saves the state of the async function and returns a promise (which nothing uses, because nothing is using the return value of init).
Now the job to run the top-level code is complete.
The thread is now idling, waiting for a job to perform (an I/O completion, timer, etc.).
After about a second of idling, a job is queued to call the first timer callback. How this happens is implementation specific:
It might be that the main thread, as part of its loop checking the queue for work, also checks the list of timers to see if any of them are due to be run.
It might be that the timer system has its own non-JavaScript thread that does those checks (or gets a callback from an OS mechanism) and queues the job when a timer callback needs to run.
In the case of I/O (since timers are a stand-in for I/O in your example), Node.js uses a separate thread or threads to process I/O completions from the operating system and queue jobs for them in the JavaScript job queue.
The JavaScript thread picks up the job to call the timer callback. It does that, which fulfills that first sleep promise.
Fulfilling a promise queues a "microtask" (or "promise job") to call the promise's fulfillment handler (then handler) rather than a "macrotask" (standard job). Modern JavaScript engines handle promise jobs at the end of the standard job that queues them (even if there's another standard job already in the main queue waiting to be done — promise jobs jump the main queue). So:
Fulfilling the first sleep promise queues a promise job.
At the end of the standard job for the timer callback, the thread picks the promise job and runs it.
The promise job triggers a call to a fulfillment handler within Promise.all's logic that stores the fulfillment value and checks to see if all of the promises it's waiting for are settled. In this case, they aren't (two are still outstanding), so there's nothing further to do and the promise job is complete.
The thread goes back to the main queue.
Almost immediately, there's a job in the queue to call the next timer callback (or possibly two jobs to call both of them).
The thread picks up the first one and does it, which fulfills the second sleep promise, queuing a promise job to run its fulfillment handler
It picks up the queued promise job and calls the fulfillment hander in Promise.all's logic, which stores the fulfillment value and checks if all the promises are settled. They aren't, so it just returns.
It picks up the next standard job and does it, which fulfills the third sleep promise, queuing a promise job for its fulfillment handler.
It picks up that promise job, running the fulfillment handler in Promise.all's logic, which stores the fulfillment result, sees that all of the promises are settled, and queues a promise job to run its fulfillment handler.
It picks up that new promise job, which advances the state of the async init function:
The init function does var t1 = performance.now();, and shows the difference between t1 and t0, which is roughly one second.
There's only one JavaScript thread involved (in that code). It's running a loop, servicing jobs in the queue. There may be a separate thread for the timers, or the main thread may just be checking the timer list between jobs. (I'd have to dig into the Node.js code to know which, but I suspect the latter because I suspect they're using OS-specific scheduling features.) If it were I/O completions instead of timers, I'm fairly sure those are handled by a separate non-JavaScript thread which responds to completions from the OS and queues jobs for the JavaScript thread.
Related
I've observed that in the following code:
setTimeout(function(){console.log('setTimeout')});
Promise.resolve(1).then(function(){console.log('promise resolve')})
No matter how many times I execute this, the promise callback always logs before the setTimeout.
My understanding is that both callbacks are scheduled to be executed to the next tick, and I don't really understand what is going on that makes the promise always take precendence over the timeout.
Promise.resolve schedules a microtask and the setTimeout schedules a macrotask. And the microtasks are executed before running the next macrotask.
Short answer Promises have better priority than setTimeout callback function in event loop stack(or how i understand it).
Long answer watch this video. Very helpful. Hope this helps.
https://www.youtube.com/watch?v=8aGhZQkoFbQ
Thanks #MickJuice for new and updated video for event loop.
https://www.youtube.com/watch?v=cCOL7MC4Pl0
setTimeout() has a minimum delay of 4ms, so even though you didn't specify a delay in your code the timeout will still be delayed at least 4ms. Your promise's .then() is called in the meantime.
Timeouts and Promises both serve to execute code in an asynchronous way but with differences characteristics and purposes:
setTimeout
- Delays the execution of a function by specific time duration.
- Does not block the rest of the code execution (asynchronous behavior)
- They create Macrotask (browser internal operation)
Promises
- They are a wrapper to allow asynchronous execution of code(Eg: an ajax call). (Does not depend on specific time duration)
- They are especially useful to chain different async calls.
- Does not block the rest of the code execution (asynchronous behavior) at less you are using the await operator.
- They create Microtask (browser internal operation), which have priority over the Macrotask.
Recommendation
Use setTimeout when you want to delay a function execution some specific time duration and not block the rest of the code execution in the process
Use Promises:
When you want to execute some async code and to avoid the “callback hell” (yes because you can make asynchronous ajax calls without Promises but the syntax is less clear and more prone to errors)
This has to do with the event loop as defined in the Web Spec. The browser has multiple task queues for multiple types of tasks (e.g. timer tasks created through setTimeout), as well as a microtask queue (where Promise settlement gets pushed to). Whenever the browser finishes executing a task, it empties the microtask queue and executes all tasks in it, before continuing with a task from another task queue.
Therefore after the code executes (which is a task), the Promise settlement is inside of the microtask queue, and the timer task might already be inside a task queue¹. The microtask queue gets emptied and the Promise resolves. Then somewhen the timer task runs.
¹ Browsers might choose to increase timeouts a bit, and they do. A timeout will never run after 0ms in most browsers.
Timeouts and Promises serve different purposes.
setTimeout delays the execution of the code block by a specific time duration. Promises are an interface to allow async execution of code.
A promise allows code to continue executing while you wait for another action to complete. Usually this is a network call. So anything in your then() call will be executed once the network call (or whatever the promise is waiting for) is completed. The time difference between the start of the promise and the resolution of the promise entirely depends on what the promise is executing, and can change with every execution.
The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.
I've seen many people saying that Promise.all can't achieve parallelism, since node/javascript runs on a single-threaded environment. However, if i, for instance, wrap 5 promises inside a Promise.all, in which every single one of the promises resolves after 3 seconds (a simple setTimeout promise), how come Promise.all resolves all of them in 3 seconds instead of something like 15 seconds (5 x 3 sec each)?
See the example below:
function await3seconds () {
return new Promise(function(res) {
setTimeout(() => res(), 3000)
})
}
console.time("Promise.all finished in::")
Promise.all([await3seconds(), await3seconds(), await3seconds(), await3seconds(), await3seconds()])
.then(() => {
console.timeEnd("Promise.all finished in::")
})
It logs:
Promise.all finished in::: 3.016s
How is this behavior possible without parallelism? Concurrent execution wouldn't be able to proccess all of these promises in 3 seconds either.
It's particularly useful to understand what this line of code actually does:
Promise.all([await3seconds(), await3seconds(), await3seconds(), await3seconds(), await3seconds()]).then(...)
That is fundamentally the same as:
const p1 = await3seconds();
const p2 = await3seconds();
const p3 = await3seconds();
const p4 = await3seconds();
const p5 = await3seconds();
Promise.all([p1, p2, p3, p4, p5]).then(...)
What I'm trying to show here is that ALL your functions are executed serially one after the other in the order declared and they have all returned BEFORE Promise.all() is even executed.
So, some conclusions from that:
Promise.all() didn't "run" anything. It accepts an array of promises and it justs monitors all those promises, collects their results in order and notifies you (via .then() or await) when they are all done or tells you when the first one rejects.
Your functions are already executed and have returned a promise BEFORE Promise.all() even runs. So, Promise.all() doesn't determine anything about how those functions run.
If the functions you were calling were blocking, the first would run to completion before the second was even called. Again, this has nothing to do with Promise.all() before the functions are all executed before Promise.all() is even called.
In your particular example, your functions each start a timer and immediately return. So, you essentially start 5 timers within ms of each other that are all set to fire in 3 seconds. setTimeout() is non-blocking. It tells the system to create a timer and gives the system a callback to call when that timer fires and then IMMEDIATELY returns. Sometime later, when the event loop is free, the timer will fire and the callback will get called. So, that's why all the timers are set at once and all fire at about the same time. If you wanted them to each be spread out by 3 seconds apart, you'd have to write this code differently, either to set increasing times for each timer or to not start the 2nd timer until the first one fires and so on.
So, what Promise.all() allows you to do is to monitor multiple asynchronous operations that are, by themselves (independent of Promise.all()) capable of running asynchronously. Nodejs itself, nothing to do with Promise.all(), has the ability to run multiple asynchronous operations in parallel. For example, you can make multiple http requests or make multiple read requests from the file system and nodejs will run those in parallel. They will all be "in flight" at the same time.
So, Promise.all() isn't enabling parallelism of asynchronous operations. That capability is built into the asynchronous operations themselves and how they interact with nodejs and how they are implemented. Promise.all() allows you to track multiple asynchronous operations and know when they are all done, get their results in order and/or know when there was an error in one of the operations.
If you're curious how timers work in nodejs, they are managed by libuv which is a cross platform library that nodejs uses for managing the event loop, timers, file system access, networking and a whole bunch of things.
Inside of libuv, it manages a sorted list of pending timers. The timers are sorted by their next time to fire so the soonest timer to fire is at the start of the list.
The event loop within nodejs goes in a cycle to check for a bunch of different things and one of those things is to see if the current head of the timer list has reached its firing time. If so, it removes that timer from the list, grabs the callback associated with that timer and calls it.
Other types of asynchronous operations such as file system access work completely differently. The asynchronous file operations in the fs module, actually use a native code thread pool. So, when you request an asynchronous file operation, it actually grabs a thread from the thread pool, gives it the job for the particular file operation you requested and sends the thread on its way. That native code thread, then runs independently from the Javascript interpreter which is free to go do other things. At some future time when the thread finishes the file operation, it calls a thread safe interface of the event loop to add a file completion event to a queue. When whatever Javascript is currently executing finishes and returns control back to the event loop, one of the things the event loop will do is check if there are any file system completion events waiting to be executed. If so, it will remove it from the queue and call the callback associated with it.
So, while individual asynchronous operation can themselves run in parallel if designed appropriately (usually backed by native code), signaling completion or errors to your Javascript all runs through the event loop and the interpreter is only running one piece of Javascript at a time.
Note: this is ignoring for the purposes of this discussion, the WorkerThread capability which actually fires up a whole different interpreter and can run multiple sets of Javascript at the same time. But, each individual interpreter still runs your Javascript single threaded and is still coordinated with the outside world through the event loop.
First, Promise.all has nothing to do with JS code running in parallel. You can think of Promise.all as code organizer, it puts code together and wait for response.
What's responsible for the code to run in a way that "looks like" it's parallel is the event-based nature of JS. So in your case:
Promise.all([await3seconds(), await3seconds(),
await3seconds(), await3seconds(), await3seconds()])
.then(() => {
console.timeEnd("Promise.all finished in::")
})
Let's say that each function inside Promise.all is called a1 : a5, What will happen is:
The Event loop will take a1 : a5 and put them in the "Callback Queue/Task Queue" sequentially (one after the other), But it will not take too much time, because it's just putting it in there, not executing anything.
The timer will start immediately after each function is put by the Event Loop in the "Callback Queue/Task Queue" (so there will be a minor delay between the start of each one).
Whenever a timer finishes, the Event loop will take the related callback function and put it in the "Call Stack" to be executed.
Promise.all will resolve after the last function is popped out of the "Call Stack".
As you can see in here
Promise.all finished in::: 3.016s
The 0.16s delay is a combination between the time the Event loop took to put those callback functions sequentially in the "Callback Queue/Task Queue" + the time each function took to execute the code inside it after their timer has finished.
So the code is not executed in parallel, it's still sequential, but the event-based nature of JS is used to mimic the behavior of parallelism.
Look at this article to relate more to what I am trying to say.
Synchronous vs Asynchronous JavaScript
No they are not executed in parallel but you can conceptualize them this way. This is just how the event queue works. If each promise contained a heavy compute task, they would still be executed one at a time -
function await3seconds(id) {
return new Promise(res => {
console.log(id, Date.now())
setTimeout(_=> {
console.log(id, Date.now())
res()
}, 3000)
})
}
console.time("Promise.all finished in::")
Promise.all([await3seconds(1), await3seconds(2), await3seconds(3), await3seconds(4), await3seconds(5)])
.then(() => {
console.timeEnd("Promise.all finished in::")
})
time
1
2
3
4
5
1621997095724
start
⌛
⌛
⌛
⌛
1621997095725
↓
start
⌛
⌛
⌛
1621997095725
↓
↓
start
⌛
⌛
1621997095725
↓
↓
↓
start
⌛
1621997095726
↓
↓
↓
↓
start
1621997098738
end
↓
↓
↓
↓
1621997098740
✓
end
↓
↓
↓
1621997098740
✓
✓
end
↓
↓
1621997098741
✓
✓
✓
end
↓
1621997098742
✓
✓
✓
✓
end
In this related Q&A we build a batch processing Pool that emulates threads. Check it out if that kind of thing interests you!
I've observed that in the following code:
setTimeout(function(){console.log('setTimeout')});
Promise.resolve(1).then(function(){console.log('promise resolve')})
No matter how many times I execute this, the promise callback always logs before the setTimeout.
My understanding is that both callbacks are scheduled to be executed to the next tick, and I don't really understand what is going on that makes the promise always take precendence over the timeout.
Promise.resolve schedules a microtask and the setTimeout schedules a macrotask. And the microtasks are executed before running the next macrotask.
Short answer Promises have better priority than setTimeout callback function in event loop stack(or how i understand it).
Long answer watch this video. Very helpful. Hope this helps.
https://www.youtube.com/watch?v=8aGhZQkoFbQ
Thanks #MickJuice for new and updated video for event loop.
https://www.youtube.com/watch?v=cCOL7MC4Pl0
setTimeout() has a minimum delay of 4ms, so even though you didn't specify a delay in your code the timeout will still be delayed at least 4ms. Your promise's .then() is called in the meantime.
Timeouts and Promises both serve to execute code in an asynchronous way but with differences characteristics and purposes:
setTimeout
- Delays the execution of a function by specific time duration.
- Does not block the rest of the code execution (asynchronous behavior)
- They create Macrotask (browser internal operation)
Promises
- They are a wrapper to allow asynchronous execution of code(Eg: an ajax call). (Does not depend on specific time duration)
- They are especially useful to chain different async calls.
- Does not block the rest of the code execution (asynchronous behavior) at less you are using the await operator.
- They create Microtask (browser internal operation), which have priority over the Macrotask.
Recommendation
Use setTimeout when you want to delay a function execution some specific time duration and not block the rest of the code execution in the process
Use Promises:
When you want to execute some async code and to avoid the “callback hell” (yes because you can make asynchronous ajax calls without Promises but the syntax is less clear and more prone to errors)
This has to do with the event loop as defined in the Web Spec. The browser has multiple task queues for multiple types of tasks (e.g. timer tasks created through setTimeout), as well as a microtask queue (where Promise settlement gets pushed to). Whenever the browser finishes executing a task, it empties the microtask queue and executes all tasks in it, before continuing with a task from another task queue.
Therefore after the code executes (which is a task), the Promise settlement is inside of the microtask queue, and the timer task might already be inside a task queue¹. The microtask queue gets emptied and the Promise resolves. Then somewhen the timer task runs.
¹ Browsers might choose to increase timeouts a bit, and they do. A timeout will never run after 0ms in most browsers.
Timeouts and Promises serve different purposes.
setTimeout delays the execution of the code block by a specific time duration. Promises are an interface to allow async execution of code.
A promise allows code to continue executing while you wait for another action to complete. Usually this is a network call. So anything in your then() call will be executed once the network call (or whatever the promise is waiting for) is completed. The time difference between the start of the promise and the resolution of the promise entirely depends on what the promise is executing, and can change with every execution.
The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.
I am a bit confused about when the event loop spins in the browser.
The questions are:
Does a task and the pending microtasks, happen in the same loop iteration/turn/tick?
Which are the actual conditions that need to be met in order for the loop to turn?
Are these conditions the same in node.js event loop? - I don't know if this is a stupid question.
Let's say we have a webpage and in the front end we have JavaScript code that schedules a task and waits for a promise (which is a microtask). Is the execution of the promise considered to happen in the same turn of the event loop as the task, or in different iterations?
I currently assume that they all happen in the same iteration. Since if betting otherwise, in the case of microtasks executing while mid-task, that would mean that the task would require multiple loop iterations in order to fully complete. Which seems wired to me. Would it be correct to also say that the update rendering part, that may occur after each task, happens in the same loop turn?
Thank you in advance!
------------------------------------------------------------------------------------
I know I am supposed to add a comment, but it is going to be a long one, and I also need to write code, so I am editing the question and asking for clarification here.
#T.J. Crowder Thank you so much for your time and detailed explanation!
I had indeed misread "microtasks are processed after callbacks (as long as no other JavaScript is mid-execution)" in this great article and had gotten a bit confused.
I also had questions about the 4ms setTimout for which I couldn't find information about, so thanks for that info also.
One last thing, though... If we were to mark the loop ticks between the example code, where would we put them (assuming console.logs do not exist)?
Suppose we have a function named exampleCode, having the following body:
setTimeout(setTimeoutCallback, 0);
Promise.resolve().then(promiseCallback);
For the above code, my guess would be...
Just before executing exampleCode (macro)task:
first loop tick
setTimeoutCallback (macro)task scheduling
Promise.then microtask scheduling
promiseCallback execution
second loop tick
setTimeoutCallback execution
third loop tick
Or is there an additional loop tick between between Promise.then microtask scheduling and promiseCallback execution ?
Thank you in advance once again!
Does a task and the pending microtasks, happen in the same loop iteration/turn/tick?
The task occurs, then when it ends, any pending microtasks it scheduled are run.
Which are the actual conditions that need to be met in order for the loop to turn?
It's not clear what you mean by this. It may be easier to think in terms of jobs and a job queue (which is the ECMAScript spec's terminology): If there is a pending job and the thread servicing that queue is not doing something else, it picks up the job and runs it to completion.
Are these conditions the same in node.js event loop?
Close enough, yes.
Let's say we have a webpage and in the front end we have JavaScript code that schedules a task and waits for a promise (which is a microtask). Is the execution of the promise considered to happen in the same turn of the event loop as the task, or in different iterations?
In a browser (and in Node), it happens after the task completes, when the task's microtasks (if any) are run, before the next queued task/job gets picked up.
For instance:
// This code is run in a task/job
console.log("Scheduling (macro)task/job");
setTimeout(() => {
console.log("timeout callback ran");
}, 0);
console.log("Scheduling microtask/job");
Promise.resolve().then(() => {
console.log("promise then callback ran");
});
console.log("main task complete");
On a compliant browser (and Node), that will output:
Scheduling (macro)task/job
Scheduling microtask/job
main task complete
promise then callback ran
timeout callback ran
...because the microtask ran when the main task completed, before the next macrotask ran.
(Note that setTimeout(..., 0) will indeed schedule the timer to run immediately on compliant browsers provided it's not a nested timeout; more here. You'll see people saying there is no "setTimeout 0", but that's outdated information. It's only clamped to 4ms if the timer's nesting level is > 5.)
More to explore:
MDN Concurrency Model and Event Loop
ECMAScript Spec: Jobs and Job Queues
WHAT-WG "HTML5" Spec: Event Loops, Processing Model, Timers, and Timer Initialization Steps
Re the code and question in the edit/comment:
setTimeout(setTimeoutCallback, 0);
Promise.resolve().then(promiseCallback);
Your guess looks pretty good. Here's how I'd describe it:
Schedule the task to run that script
(When thread is next free)
Pick up the next task (from #1 above)
Run that task:
Create the task for the timer callback
In parallel, queue the task when the time comes
Queue a microtask for the promise then callback
End of task
Microtask check
Run the then callback
(When thread is next free)
Pick up the next task (the one for the timer callback)
Run the task
End of task
Microtask check (none to do in this case)
The processing model text explicitly calls out that the task ends prior to the microtask check, but I don't think that's observable in any real way.
I've observed that in the following code:
setTimeout(function(){console.log('setTimeout')});
Promise.resolve(1).then(function(){console.log('promise resolve')})
No matter how many times I execute this, the promise callback always logs before the setTimeout.
My understanding is that both callbacks are scheduled to be executed to the next tick, and I don't really understand what is going on that makes the promise always take precendence over the timeout.
Promise.resolve schedules a microtask and the setTimeout schedules a macrotask. And the microtasks are executed before running the next macrotask.
Short answer Promises have better priority than setTimeout callback function in event loop stack(or how i understand it).
Long answer watch this video. Very helpful. Hope this helps.
https://www.youtube.com/watch?v=8aGhZQkoFbQ
Thanks #MickJuice for new and updated video for event loop.
https://www.youtube.com/watch?v=cCOL7MC4Pl0
setTimeout() has a minimum delay of 4ms, so even though you didn't specify a delay in your code the timeout will still be delayed at least 4ms. Your promise's .then() is called in the meantime.
Timeouts and Promises both serve to execute code in an asynchronous way but with differences characteristics and purposes:
setTimeout
- Delays the execution of a function by specific time duration.
- Does not block the rest of the code execution (asynchronous behavior)
- They create Macrotask (browser internal operation)
Promises
- They are a wrapper to allow asynchronous execution of code(Eg: an ajax call). (Does not depend on specific time duration)
- They are especially useful to chain different async calls.
- Does not block the rest of the code execution (asynchronous behavior) at less you are using the await operator.
- They create Microtask (browser internal operation), which have priority over the Macrotask.
Recommendation
Use setTimeout when you want to delay a function execution some specific time duration and not block the rest of the code execution in the process
Use Promises:
When you want to execute some async code and to avoid the “callback hell” (yes because you can make asynchronous ajax calls without Promises but the syntax is less clear and more prone to errors)
This has to do with the event loop as defined in the Web Spec. The browser has multiple task queues for multiple types of tasks (e.g. timer tasks created through setTimeout), as well as a microtask queue (where Promise settlement gets pushed to). Whenever the browser finishes executing a task, it empties the microtask queue and executes all tasks in it, before continuing with a task from another task queue.
Therefore after the code executes (which is a task), the Promise settlement is inside of the microtask queue, and the timer task might already be inside a task queue¹. The microtask queue gets emptied and the Promise resolves. Then somewhen the timer task runs.
¹ Browsers might choose to increase timeouts a bit, and they do. A timeout will never run after 0ms in most browsers.
Timeouts and Promises serve different purposes.
setTimeout delays the execution of the code block by a specific time duration. Promises are an interface to allow async execution of code.
A promise allows code to continue executing while you wait for another action to complete. Usually this is a network call. So anything in your then() call will be executed once the network call (or whatever the promise is waiting for) is completed. The time difference between the start of the promise and the resolution of the promise entirely depends on what the promise is executing, and can change with every execution.
The reason the promise is executing before your timeout is that the promise isn't actually waiting for anything so it resolved right away.