Javascript (not just node.js but even your web browser regardless if it's Chrome or Safari or IE or Edge) is single threaded. The structure of the javascript interpreter can generally be described by the following pseudocode:
script_to_exec = compile_js_from_files()
do {
event_queue = execute_javascript(script_to_exec)
completed_events = wait_for_events()
for_each (event from completed_events) {
this_event = event_queue.find_one_matching(event)
script_to_exec = this_event.callback
}
} while (there_is_pending_events)
You may notice that there is no threading code at all in the above code. All we are doing in wait_for_events()
is to use the async I/O API provided by the OS. Depending on the OS this can be something like the POSIX select()
or the Linux and OSX poll()
or BSD epoll()
or Windows overlapped I/O functions. Node uses the libuv library which will automatically select the best API to use at compile time.
Loop round 1
Ok. So first round of the loop it executes your entire code. This sets up a TCP socket listener when you set up express. Then it gets stuck at wait_for_events()
The first HTTP request causes wait_for_events()
to return. This causes Express to go through your list of middlewares and routes to find a route to execute. It finds your route and calls wait()
which calls setTimeout()
which adds its callback to the list of timers.
Loop round 2
Since there is no more javascript to execute until await
returns we go round the loop again and get stuck at wait_for_events()
again.
The second HTTP request causes wait_for_events()
to return. This causes Express to go through your list of middlewares and routes and repeat what I described previously.
Loop round 3
Since there is no more javascript to execute until await
returns we go round the loop again and get stuck at wait_for_events()
again.
The third HTTP request causes wait_for_events()
to return. This causes Express to go through your list of middlewares and routes and repeat what I described previously.
Loop round 4
Again, we go round the loop again and get stuck at wait_for_events()
.
The first await wait()
timer expires. This causes wait_for_events()
to return and javascript figures out that the event is the timer for that wait
therefore it continues processing the first HTTP request. This causes Express to send a response.
Loop round 5
We go round the loop again and wait for wait_for_events()
.
The second await wait()
timer expires. This causes wait_for_events()
to return and javascript figures out that the event is the timer for that wait
therefore it continues processing the second HTTP request. This causes Express to send a response.
Loop round 6
We go round the loop again and wait for wait_for_events()
.
The third await wait()
timer expires. This causes wait_for_events()
to return and javascript figures out that the event is the timer for that wait
therefore it continues processing the third HTTP request. This causes Express to send a response.
Loop round 7
We go round the loop again and get stuck at wait_for_events()
again waiting for more HTTP requests...
Summary
Basically javascript can wait for multiple things in parallel. This evolved out of the fact that it started as a GUI scripting language that had to have the ability to wait for things like mouse click, keyboard input etc. But it can only execute one instruction at a time (unless of course you deliberately use features to execute additional processes/threads such as web workers or worker threads).
If you're wondering why people use node.js for high performance web servers I have written an answer to this related question explaining why node is fast: Node js architecture and performance
Here's some of my other answers going into different levels of details about asynchronous execution:
I know that callback function runs asynchronously, but why?
Is my understanding of asynchronous operations correct?
Is there any other way to implement a "listening" function without an infinite while loop?
node js - what happens to incoming events during callback excution
I know that callback function runs asynchronously, but why?