Node.js: hvad det er, hvornår og hvordan man bruger det, og hvorfor du skal

Du har sandsynligvis læst disse sætninger før ...

Node.js er en JavaScript-runtime bygget på Chromes V8 JavaScript-motorNode.js bruger en hændelsesdrevet, asynkron ikke-blokerende I / O-modelNode.js fungerer på en enkelt trådhændelsesløkke

... og blev efterladt at undre sig over hvad alt dette betød. Forhåbentlig ved udgangen af ​​denne artikel får du en bedre forståelse af disse vilkår såvel som om hvad Node er, hvordan det fungerer, og hvorfor og hvornår er det en god ide at bruge det.

Lad os starte med at gå over terminologien.

I / O (input / output)

Kort for input / output henviser I / O primært til programmets interaktion med systemets disk og netværk. Eksempler på I / O-operationer inkluderer læsning / skrivning af data fra / til en disk, fremsættelse af HTTP-anmodninger og samtale med databaser. De er meget langsomme sammenlignet med adgang til hukommelse (RAM) eller arbejde på CPU'en.

Synkron versus asynkron

Synkron udførelse (eller synkronisering) refererer normalt til kodeudførelse i rækkefølge. I synkroniseringsprogrammering udføres programmet linje for linje, en linje ad gangen. Hver gang der kaldes på en funktion, venter programudførelsen, indtil denne funktion vender tilbage, før den fortsætter til næste kodelinje.

Asynkron (eller asynk) udførelse henviser til udførelse, der ikke kører i den rækkefølge, den vises i koden. Ved async-programmering venter programmet ikke på, at opgaven er afsluttet, og kan gå videre til næste opgave.

I det følgende eksempel får synkroniseringsfunktionen alarmer til at udløses i rækkefølge. Mens asynkroniseringen virker, mens alarm (2) ser ud til at udføre sekund, gør den det ikke.

// Synchronous: 1,2,3 alert(1); alert(2); alert(3); // Asynchronous: 1,3,2 alert(1); setTimeout(() => alert(2), 0); alert(3);

En asynkronisering er ofte I / O-relateret, selvom det setTimeouter et eksempel på noget, der ikke er I / O, men stadig asynkroniseres. Generelt er alt beregningsrelateret synkroniseret, og alt input / output / timing-relateret er asynkroniseret. Årsagen til, at I / O-operationer udføres asynkront, er at de er meget langsomme og ellers ville blokere for yderligere udførelse af kode.

Blokering vs Ikke-blokering

Blokering henviser til operationer, der blokerer for yderligere udførelse, indtil den handling er afsluttet, mens ikke-blokering henviser til kode, der ikke blokerer udførelse. Eller som Node.js docs udtrykker det, er blokering, når udførelsen af ​​yderligere JavaScript i Node.js-processen skal vente, indtil en ikke-JavaScript-handling er afsluttet.

Blokeringsmetoder udføres synkront, mens ikke-blokerende metoder udføres asynkront.

// Blocking const fs = require('fs'); const data = fs.readFileSync('/file.md'); // blocks here until file is read console.log(data); moreWork(); // will run after console.log // Non-blocking const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data); }); moreWork(); // will run before console.log

I det første eksempel ovenfor console.logvil blive kaldt før moreWork(). I det andet eksempel fs.readFile()er ikke-blokering, så JavaScript-udførelse kan fortsætte og moreWork()kaldes først.

I node henviser ikke-blokering primært til I / O-operationer, og JavaScript, der udviser dårlig ydeevne på grund af at være CPU-intensiv snarere end at vente på en ikke-JavaScript-operation, såsom I / O, kaldes ikke typisk blokering.

Alle I / O-metoderne i Node.js-standardbiblioteket leverer async-versioner, som ikke er blokerende, og accepterer tilbagekaldsfunktioner. Nogle metoder har også blokerende modstykker, som har navne, der slutter med Sync.

Ikke-blokerende I / O-operationer tillader, at en enkelt proces serverer flere anmodninger på samme tid. I stedet for at processen blokeres og venter på, at I / O-operationer er afsluttet, delegeres I / O-operationerne til systemet, så processen kan udføre det næste stykke kode. Ikke-blokerende I / O-operationer giver en tilbagekaldsfunktion, der kaldes, når operationen er afsluttet.

Tilbagekald

En tilbagekaldelse er en funktion, der sendes som et argument til en anden funktion, som derefter kan påberåbes (kaldes tilbage) inde i den ydre funktion for at fuldføre en slags handling på et passende tidspunkt. Indkaldelsen kan være øjeblikkelig (synkroniser tilbagekald), eller det kan ske på et senere tidspunkt (asynk. Tilbagekald).

// Sync callback function greetings(callback) { callback(); } greetings(() => { console.log('Hi'); }); moreWork(); // will run after console.log // Async callback const fs = require('fs'); fs.readFile('/file.md', function callback(err, data) { // fs.readFile is an async method provided by Node if (err) throw err; console.log(data); }); moreWork(); // will run before console.log 

I det første eksempel kaldes tilbagekaldsfunktionen straks inden for den ydre hilsenfunktion og logger på konsollen, før den moreWork()fortsætter.

I det andet eksempel læser fs.readFile (en async-metode leveret af Node) filen, og når den er færdig, kalder den tilbagekaldsfunktionen med en fejl eller filindholdet. I mellemtiden kan programmet fortsætte kodekørsel.

En asynkron tilbagekaldelse kan kaldes, når en begivenhed sker, eller når en opgave er afsluttet. Det forhindrer blokering ved at lade anden kode udføres i mellemtiden.

I stedet for kodelæsning fra top til bund procedurelt kan asynkroniseringsprogrammer udføre forskellige funktioner på forskellige tidspunkter baseret på rækkefølgen og hastigheden, som tidligere funktioner som http-anmodninger eller filsystem læser, sker. De bruges, når du ikke ved, hvornår en asynkronisering er afsluttet.

Du bør undgå " tilbagekald helvede ", en situation, hvor tilbagekald er indlejret i andre tilbagekald flere niveauer dybt, hvilket gør koden vanskelig at forstå, vedligeholde og fejlagtigt.

Begivenheder og begivenhedsdrevet programmering

Begivenheder er handlinger, der genereres af brugeren eller systemet, som f.eks. Et klik, en færdig fildownload eller en hardware- eller softwarefejl.

Begivenhedsdrevet programmering er et programmeringsparadigme, hvor strømmen af ​​programmet bestemmes af begivenheder. Et begivenhedsdrevet program udfører handlinger som reaktion på begivenheder. Når en begivenhed opstår, udløser den en tilbagekaldsfunktion.

Lad os nu prøve at forstå Node og se, hvordan alle disse vedrører det.

Node.js: hvad er det, hvorfor blev det oprettet, og hvordan fungerer det?

Kort sagt er Node.js en platform, der udfører JavaScript-programmer på serversiden, der kan kommunikere med I / O-kilder som netværk og filsystemer.

Da Ryan Dahl oprettede Node i 2009, argumenterede han for, at I / O blev håndteret forkert og blokeret hele processen på grund af synkron programmering.

Traditionelle web-serveringsteknikker bruger trådmodellen, hvilket betyder en tråd for hver anmodning. Da anmodningen i en I / O-operation bruger det meste af tiden på at afslutte, medfører intensive I / O-scenarier en stor mængde ubrugte ressourcer (såsom hukommelse), der er knyttet til disse tråde. Derfor ”en tråd pr. Anmodning” -model for en server skaleres ikke godt.

Dahl argued that software should be able to multi-task and proposed eliminating the time spent waiting for I/O results to come back. Instead of the thread model, he said the right way to handle several concurrent connections was to have a single-thread, an event loop and non-blocking I/Os. For example, when you make a query to a database, instead of waiting for the response you give it a callback so your execution can run through that statement and continue doing other things. When the results come back you can execute the callback.

The event loop is what allows Node.js to perform non-blocking I/O operations despite the fact that JavaScript is single-threaded. The loop, which runs on the same thread as the JavaScript code, grabs a task from the code and executes it. If the task is async or an I/O operation the loop offloads it to the system kernel, like in the case for new connections to the server, or to a thread pool, like file system related operations. The loop then grabs the next task and executes it.

Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes (this is an event), the kernel tells Node.js so that the appropriate callback (the one that depended on the operation completing) may be added to the poll queue to eventually be executed.

Node keeps track of unfinished async operations, and the event loop keeps looping to check if they are finished until all of them are.

To accommodate the single-threaded event loop, Node.js uses the libuv library, which, in turn, uses a fixed-sized thread pool that handles the execution of some of the non-blocking asynchronous I/O operations in parallel. The main thread call functions post tasks to the shared task queue, which threads in the thread pool pull and execute.

Inherently non-blocking system functions such as networking translate to kernel-side non-blocking sockets, while inherently blocking system functions such as file I/O run in a blocking way on their own threads. When a thread in the thread pool completes a task, it informs the main thread of this, which in turn, wakes up and executes the registered callback.

The above image is taken from Philip Roberts’ presentation at JSConf EU: What the heck is the event loop anyway? I recommend watching the full video to get a high level idea about how the event loop works.

The diagram explains how the event loop works with the browser but it looks basically identical for Node. Instead of web APIs we would have Node APIs.

According to the presentation, the call stack (aka execution stack or “the stack”) is a data structure which records where in the program we are. If we step into a function, we put something onto the stack. If we return from a function, we pop it off the top of the stack.

This is how the code in the diagram is processed when we run it:

  1. Push main() onto the stack (the file itself)
  2. Push console.log(‘Hi’); onto the stack, which executes immediately logging “Hi” to the console and gets popped off the stack
  3. Push setTimeout(cb, 5000) onto the stack. setTimeout is an API provided by the browser (on the backend it would be a Node API). When setTimeout is called with the callback function and delay arguments, the browser kicks off a timer with the delay time
  4. The setTimeout call is completed and gets popped off the stack
  5. Push console.log(‘JSConfEU’); onto the stack, which executes immediately logging “JSConfEU” to the console and gets popped off the stack
  6. main() gets popped off the stack
  7. After 5000 milliseconds the API timer completes and the callback gets moved to the task queue
  8. The event loop checks if the stack is empty because JavaScript, being single-threaded, can only do one thing at a time (setTimeout is not a guaranteed but a minimum time to execution). If the stack is empty it takes the first thing on the queue and pushes it onto the stack. Therefore the loop pushes the callback onto the stack
  9. The callback gets executed, logs “there” to the console and gets popped off the stack. And we are done

If you want to go even deeper into the details on how Node.js, libuv, the event loop and the thread pool work, I suggest checking the resources on the reference section at the end, in particular this, this and this along with the Node docs.

Node.js: why and where to use it?

Since almost no function in Node directly performs I/O, the process never blocks (I/O operations are offloaded and executed asynchronously in the system), making it a good choice to develop highly scalable systems.

Due to its event-driven, single-threaded event loop and asynchronous non-blocking I/O model, Node.js performs best on intense I/O applications requiring speed and scalability with lots of concurrent connections, like video & audio streaming, real-time apps, live chats, gaming apps, collaboration tools, or stock exchange software.

Node.js may not be the right choice for CPU intensive operations. Instead the traditional thread model may perform better.

npm

npm is the default package manager for Node.js and it gets installed into the system when Node.js is installed. It can manage packages that are local dependencies of a particular project, as well as globally-installed JavaScript tools.

www.npmjs.com hosts thousands of free libraries to download and use in your program to make development faster and more efficient. However, since anybody can create libraries and there’s no vetting process for submission, you have to be careful about low quality, insecure, or malicious ones. npm relies on user reports to take down packages if they violate policies, and to help you decide, it includes statistics like number of downloads and number of depending packages.

How to run code in Node.js

Start by installing Node on your computer if you don’t have it already. The easiest way is to visit nodejs.org and click to download it. Unless you want or need to have access to the latest features, download the LTS (Long Term Support) version for you operating system.

You run a Node application from your computer’s terminal. For example make a file “app.js” and add console.log(‘Hi’); to it. On your terminal change the directory to the folder where this file belongs to and run node app.js. It will log “Hi” to the console. ?

References

Here are some of the interesting resources I reviewed during the writing of the article.

Node.js presentations by its author:

  • Original Node.js presentation by Ryan Dahl at JSConf 2009
  • 10 Things I Regret About Node.js by Ryan Dahl at JSConf EU 2018

Node, begivenhedssløjfen og libuv-bibliotekspræsentationer:

  • Hvad i helvede er event loop? af Philip Roberts hos JSConf EU
  • Node.js Forklaret af Jeff Kunkle
  • In The Loop af Jake Archibald på JSConf Asia 2018
  • Alt hvad du behøver at vide om Node.js Event Loop af Bert Belder
  • Et dybt dyk i libuv af Saul Ibarra Coretge på NodeConf EU 2016

Node dokumenter:

  • Om Node.js
  • Node.js hændelsesløkke, timere og process.nextTick ()
  • Oversigt over blokering vs ikke-blokering

Yderligere ressourcer:

  • Art of Node af Max Ogden
  • Callback helvede af Max Ogden
  • Hvad er ikke-blokerende eller asynkron I / O i Node.js? på Stack Overflow
  • Begivenhedsdrevet programmering på Wikipedia
  • Node.js på Wikipedia
  • Tråd på Wikipedia
  • libuv

Tak for læsningen.