I've been doing a lot of research into the core of Node.js lately, and I have some questions about the inner workings of the Node platform. As I understand it, Node.js works like this:
Node has an API, written in Javascript, that allows the programmer to interact with things like the filesystem and network. However, all of that functionality is actually done by C/C++ code, also a part of Node. Here is where things get a little fuzzy. So it's the Chrome V8 engine's job to essentially "compile"(interpret?) javascript down into machine code. V8 is written in C++, and the Javascript language itself is specified by ECMA, so things such as keywords and features of the language are defined by them. This leads me to my first few questions:
How is the Node Standard Library able to interact with the Node Bindings, since the Node Bindings are written in C++?
How does the Chrome V8 engine interpret Javascript in the context of Node? I know it uses a technique called JIT, which was mentioned in a similar question: (https://softwareengineering.stackexchange.com/questions/291230/how-does-chrome-v8-work-and-why-was-javascript-not-jit-compiled-in-the-first-pl)
But this doesn't explain how Javascript is interpreted in the context of Node. Is the Chrome V8 engine that ships with Node the exact same engine that runs on the Chrome browser, or has it been modified to work with Node?
That brings me to my next question. So Node features event-driven, non-blocking IO. It accomplishes this via the Event Loop, which, although it is often referred to as the "Node Event Loop", is actually a part of the libuv library, a C++ library designed to provide asynchronous IO. At a high level, the event loop is essentially accessed via Callbacks, which is a native Javascript feature and one of the reasons Javascript was chosen as the language for the Node project. Below is an illustration of the how the event loop works:
This can also be demonstrated live by this neat little site: http://latentflip.com/loupe/
Let's say our Node application needs to make a call to an external API. So we write this:
request(..., function eyeOfTheTiger() {
console.log("Rising up to the challenge of our rival");
});
Our call to request
gets pushed onto the call stack, and our callback is passed somewhere, where it is kept until the request operation finishes. When it does, the callback is passed onto the callback queue. Every time the call stack is cleared, the event loop pushes the item at the top of the callback queue onto the call stack, where it is executed. This event loop is run on a single thread. Where problems arise is when someone writes 'blocking' code, or code that never leaves the call stack and effectively ties up the thread. If there is always code executing on the call stack, than the event loop will never push items from the callback queue onto the call stack and they will never get executed, essentially freezing the application. This leads me to my next question:
- If the Javascript is interpreted by the Chrome V8 engine, than what "controls" the pushing of code onto the callback queue? How is Javascript code handled by the libuv event loop?
I've found this image as a demonstration of the process:
This is where I'm uncertain about how exactly Chrome V8 engine and libuv interact. I'm inclined to believe that the Node Bindings facilitate this interaction, but I'm not quite sure how. In the image above, it appears as though the NodeJS bindings are only interacting with machine code that has been compiled down from Javascript by V8. If so, than I am confused as to how the V8 engine interprets the Javascript in such a way that the Node Bindings can differentiate between the callback and the actual code to immediately execute.
I know this is a very deep and complicated series of questions, but I believe this will help to clear up a lot of confusion for people trying to understand Node.js, and also help programmers to understand the advantages and disadvantages of event-driven, non-blocking IO at a more fundamental level.
Status Update: Just watched a fantastic talk from a Sencha conference(Link here). So in this talk, the presenter mentions the V8 embed guide(Link here), and talks about how C++ functions can be exposed to Javascript and vice versa. Essentially how it works is that C++ functions can be exposed to V8 and also specify how it wants those objects to be exposed to Javascript, and the V8 interpreter will be able to recognize your embedded C++ functions and execute them if it finds Javascript that matches what you specified. For example, you can expose variables and functions to V8 that are actually written in C++. This is essentially what Node.js does; it is able to add functions like require
into Javascript that actually execute C++ code when they are called. This clears up question number 1 a little, but it doesn't exactly show how the Node standard library works in conjunction with V8. It is still also unclear about how libuv is interacting with any of this.
See Question&Answers more detail:
os