Node.js from an outsider's perspective

Author : xuzhiping   2022-11-15 15:50:57 Browse: 1107
Category : JavaScript

Abstract: I have to contact Node.js for work reasons. Although the language is not important for good programmers, it is very important to...

I have to contact Node.js for work reasons. Although the language is not important for good programmers, it is very important to choose a language suitable for the use scenario when completing a specific task. In the process of learning and using Node.js, I have gained a lot of insights, corrected some existing prejudices, and also generated some new prejudices. But the most important thing is to have a new understanding of many things.

Speaking of Node.js, the first reaction of most people (me) is that it is just another Web framework using JavaScript. JavaScript is a terrible language, so this framework must be difficult to use. Don't rush to scold me when you see here. That's right, but it's also wrong. From a strict point of view, in addition to using JavaScript as the development language, it is also a strict event driven service framework. At the same time, it only has the ability to run in a single thread (without the introduction of workers). Of course, because the language uses JavaScript, it is very natural to introduce these two points. From a practical point of view, the biggest difference between JavaScript and other languages is that its position in the Web is almost unshakable. Today, JavaScript is still the language with the largest number of users, which determines that Node.js's ecology must be a hundred flowers blooming (not necessarily a good thing). Although JavaScript was born when there are many problems in its design that are still roast by people, with the efforts of generations, the latest standard JavaScript also has the appearance that some modern languages should have. Even if we further use the improved version of JavaScript (yes, I mean TypeScript), the development experience is even better than some other languages.

JavaScript

As you can see from the name, Node JavaScript and JavaScript are deeply bound. First, let's talk about JavaScript. When JavaScript was just born, its goal was to do some simple operations on DOM in the browser and run some relatively simple logic when there were UI events (the user clicked the button). Therefore, its characteristics are mainly rough, simple and easy to use. Later, as we all know, the Internet revolution was out of control, and JavaScript became the browser's official language by mistake. Because of concerns about forward compatibility, browsers bound themselves to this inexplicable language, and went further and further along this wrong path.

Although in the previous paragraph, JavaScript was slandered as a poor student who miraculously got all the multiple choice questions right and won the first place in the class, today's JavaScript still absorbs many features of modern languages, and tries to hide the remaining dross out of the sight of newcomers. ECMAScript releases new standards every year, which also makes the life of front-end developers better and better (except for those that need to consider forward compatibility), modern JavaScript is almost a good language. Of course, "Not bad" doesn't make JavaScript stand out from other languages. The most important thing is that it has the largest developer community in the world, which means its ecosystem is unparalleled. In addition to the countless libraries on NPM, JavaScript has the blessing of the V8 compiler developed by Google, which makes it an order of magnitude faster than Python, Ruby and other languages that are also dynamic languages. This has laid the advantages of JavaScript on the server, and Node.js is not surprising.

Benchmark

What is Node.js?

In essence, Node Js mainly relies on the powerful performance of the V8 engine, and on this basis expands the network IO, file IO, system call and other functions required by the server environment, which determines that the nature of the V8 engine is often also Node the nature of js, such as single thread, event triggering, JIT optimization and other running features, is realized by using libuv at the bottom of V8, so it can achieve good results in IO intensive applications. In a sense, Node Js combines the ease of use of JavaScript and the high performance of libuv in handling network events, which makes it practical.

Sure Node Js is also not good at it, which is also the reason for its single thread event triggering feature. If you want to develop a CPU intensive application, Node.js is definitely the last, because Node The js eventloop has only one thread. When a code that has been running for a long time is running, all other events will be blocked and cannot be triggered, which is equivalent to the whole service being unable to respond. This is unacceptable. One way to alleviate this situation is to release the control right manually at regular intervals in the computationally intensive code, so that eventloop can handle other events before continuing the calculation. However, this still cannot solve the problem of only one thread and limited computing power. Another method is to use worker threads to run calculation intensive functions. It is also a benefit to use worker threads when the calculations to be done are relatively scattered and simple. However, considering that JavaScript itself is not as good as many compilation execution languages and worker threads are still a new function, when it is necessary to develop a service mainly composed of calculation intensive code, it is still necessary to switch to Java, C++, Go and other languages.

node_arch

async and await

Those who have been exposed to asynchronous programming should not be unfamiliar with these two methods. Yes, asynchronous programming in Node.js is also implemented using async/await, and using Node Js must use asynchronous programming (no multithreading option). Of course, you can also choose other asynchronous programming methods besides async/await. For example, the following historical practice will be seen in some ancient libraries. Asynchronous programming in JavaScript has been developed step by step (along with other languages). The earliest callback form is the most direct and closest to the underlying implementation (event driven). Now, async/await makes it easier to write programs, But if you don't know the principle, you only know that it is needed when IO operations are involved, and you have no idea what happened behind it (of course, this design is very successful, allowing programmers to get away from the underlying details).

The earliest JavaScript only has a callback. Because the implementation of JavaScript is event triggered, it does not have the concept of blocking operations. If you want to call an IO function, you can only provide additional callback functions to tell the JavaScript engine how to handle the IO function when it returns data. The most classic is probably the setTimeout function.

setTimeout(function() {
console.log('beep')
}, 1000)

However, there is a problem that when we have many IO calls, our callback functions will have many layers of nesting, and the code will become difficult to read. This is the so-called callback hell. Here is just a simple example to show the possible situation.

A.call((result) => {
    doSomething(resultA)
    B.call(resultA, (resultB) => {
        doSomethingMore(resultB)
        C.call(resultB, (resultC) => {
            doSomethingcc(resultC)
        }, errCallback)
    }, errCallback)
}, errCallback)
function errCallback(err) {
    console.log(err)
}

Callback hell is not only a headache, but also easy for programmers to lose themselves in multi nesting and write bugs that would not normally occur. In order to solve this problem, people invented Promise to replace the callback function. Essentially, Promise mainly changes the way of transferring callback functions and the way of transferring parameters between IO operations. We reorganize the code and replace the previous callback function with Promise, so that the above code can finally be reconstructed into this form.

A.call()
 .then((resultA) => {
     doSomething(resultA)
     return B.call(resultA)
 })
 .then((resultB) => {
     doSomethingMore(resultB)
     return C.call(resultB)
 })
 .then((resultC) => {
     doSomethingcc(resultC)
 })
 // register error handling function
 .catch((err) => {
  console.log(err)
 })

We can see that the nested structure in the code has disappeared. Promise successfully flattens the structure of the program and is more consistent with our thinking habits of sequential execution. The use of then and catch has elegantly implemented the functions of passing values and catching exceptions. Then can we further make asynchronous programming almost the same as ordinary programming? In fact, Promise is almost over when programmers using JavaScript can do it, but it is OK for language developers to go further. On the basis of the original JavaScript, ES2017 has standardized the use of async/await functions, introducing easy to write and readable asynchronous programming into the JavaScript world. Essentially, async/await does not add anything new to Promise, it is equivalent to adding a layer of syntax sugar to Promise. The underlying layer is still implemented by Promise, but the compiler automatically compiles it into Promise form by analyzing the syntax tree. The above Promise implementation is implemented by async as follows:

try {
    const resultA = await A.call()
    doSomething(resultA)
    const resultB = await B.call(resultA)
    doSomethingMore(resultB)
    const resultC = await C.call(resultB)
    doSomethingcc(resultC)
} catch (err) {
    console.log(err)
}

Modern Node Js applications basically use async/await for asynchronous programming, which has better readability and will not affect the original performance. Compared with the server framework of multithreading (including application level threads), async/await does not lose much readability and ease of use, and it is also an advantage to separate the use of async for IO operations in a sense. Anyway, Node The nature of js asynchronous single thread determines that only asynchronous programming can be used. The difference is only in what way.

Everything that surrounds Node

When starting Node The first tool that js came into contact with during development was its package manager, npm. There have been many criticisms and complaints about npm, which makes it seem to be the existence of thousands of people. It is interesting that as a package manager, npm itself does not seem to have any incurable shortcomings. The dependency management of npm itself is not very different from other tools. A project is nothing but a package that defines project dependencies json and the node on which the installation depends_ Modules folder. For each project, it depends on the node installed locally_ In the modules directory, this can effectively prevent dependency conflicts between different projects. It is a good practice.

When we criticize node_modules are too heavy, we are actually accusing package developers in the JavaScript ecosystem of recklessly adding dependencies. Sometimes even when it is clear that only one or two very simple functions can be used, developers will add a very heavy library as a dependency, thereby introducing more dependencies on which the library depends, resulting in an irreversible situation. Compared with Python, which is also a dynamic language (pip itself has more problems), it is easy to infer that Node.js' dilemma is mainly due to the lack of an official and powerful standard library, which has led to the situation that a hundred libraries for various functions are in full bloom. When people need a common function (such as deep copy, generating random integers), their first reaction is to install a library, Even libraries that implement the same function may have several different alternatives (different libraries may also reference different dependencies in order to implement the same function). No wonder node_modules will continue to expand.

node_modules

Node.js services are often at the top of the business, and have gradually taken a place in business development today when everyone advocates microservice architecture. However, some problems with complex business logic and JavaScript are exposed. Many people claim that the dynamic type nature of JavaScript allows us to develop quickly, but in fact, dynamic type brings many problems to project maintenance and development. Here are two arguments:

First of all, the simplicity and writability of JavaScript mainly comes from the dynamic nature of its objects. Its prototype mechanism and the flexibility of objects allow programmers to operate on Object in code at will, so that they can write code quickly without drafting. Secondly, there is almost no advantage in allowing variables to have dynamic types (you hardly want a local variable to be number type first, and then string type, or a function to return numbers or strings sometimes), but you will lose all the benefits of static types (compiler automatic completion and static type checking). I can write another article about the harmfulness of dynamic typing. I won't repeat it here. Fortunately, we have TypeScript, which introduces a powerful type system while being as compatible with JavaScript as possible, and greatly improves Node For the development experience and maintainability of the js service, at least there will be no undefined bugs caused by accidentally writing wrong variable names.

At present, most of the libraries maintained by some people support TypeScript types, and many libraries gradually migrate the source code to TypeScript implementations. In a word, if you want to use Node.js to develop services, you can use TypeScript.

Label :
    Sign in for comments!
Comment list (0)

Powered by TorCMS (https://github.com/bukun/TorCMS).