Non conosci Nodo

Aggiornamento: questo articolo fa ora parte del mio libro "Node.js Beyond The Basics".
Leggi la versione aggiornata di questo contenuto e altro su Nodo su jscomplete.com/node-beyond-basics.

Alla conferenza Forward.js di quest'anno (una JavaScript), ho tenuto un discorso dal titolo "Non conosci il nodo". In quel discorso, ho sfidato il pubblico con una serie di domande sul runtime di Nodejs e la maggior parte del pubblico tecnico non ha potuto rispondere alla maggior parte delle domande.

Non l'ho davvero misurato, ma certamente mi sono sentito così nella stanza e alcune persone coraggiose mi hanno avvicinato dopo il discorso e hanno confessato il fatto.

Ecco il problema che mi ha fatto parlare. Non credo che insegniamo a Node nel modo giusto! La maggior parte dei contenuti educativi su Nodejs si concentra sui pacchetti Node e non sul suo runtime. La maggior parte di questi pacchetti include moduli nel runtime del nodo stesso (come http o stream). Quando si verificano problemi, questi potrebbero essere all'interno del runtime stesso e se non si conosce il runtime del nodo, ci si trova in difficoltà.

Il problema: la maggior parte dei contenuti educativi su Nodejs si concentra sui pacchetti Node e non sul suo runtime.

Ho scelto alcune domande e risposte per presentare questo articolo. Sono presentati nelle intestazioni seguenti. Prova a rispondere prima nella tua testa!

Se trovi una risposta errata o fuorviante qui, per favore fatemelo sapere.

Domanda n. 1: che cos'è lo stack di chiamate e fa parte di V8?

Il Call Stack fa sicuramente parte del V8. È la struttura di dati che V8 utilizza per tenere traccia delle chiamate alle funzioni. Ogni volta che invochiamo una funzione, V8 inserisce un riferimento a quella funzione nello stack di chiamate e continua a farlo per ogni invocazione nidificata di altre funzioni. Ciò include anche funzioni che si chiamano ricorsivamente.

Screenshot catturato dal mio corso Pluralsight - Advanced Node.js

Quando le invocazioni nidificate di funzioni giungono al termine, V8 farà apparire una funzione alla volta e utilizzerà il valore restituito al suo posto.

Perché è importante capire per Node? Perché ottieni solo UNO stack di chiamate per nodo. Se si tiene occupato lo stack di chiamate, l'intero processo del nodo è occupato. Tienilo a mente.

Domanda n. 2: che cos'è il Loop degli eventi? Fa parte di V8?

Dove pensi che sia il loop degli eventi in questo diagramma?

Screenshot catturato dal mio corso Pluralsight - Advanced Node.js

Il loop degli eventi è fornito dalla libreria libuv. Non fa parte di V8.

Il Loop eventi è l'entità che gestisce gli eventi esterni e li converte in invocazioni di callback. È un ciclo che seleziona gli eventi dalle code degli eventi e inserisce i loro callback nello stack di chiamate. È anche un ciclo multifase.

Se è la prima volta che senti il ​​Loop degli eventi, queste definizioni non saranno così utili. L'Event Loop fa parte di un quadro molto più ampio:

Screenshot catturato dal mio corso Pluralsight - Advanced Node.js

Devi capire il quadro più ampio per comprendere il Loop degli eventi. È necessario comprendere il ruolo di V8, conoscere le API dei nodi e sapere come le cose vengono messe in coda per essere eseguite da V8.

Le API del nodo sono funzioni come setTimeout o fs.readFile. Questi non fanno parte di JavaScript stesso. Sono solo funzioni fornite da Node.

L'Event Loop si trova al centro di questa immagine (una versione più complessa di essa, in realtà) e si comporta come un organizzatore. Quando V8 Call Stack è vuoto, il loop degli eventi può decidere cosa eseguire successivamente.

Domanda n. 3: cosa farà Node quando le code di stack di chiamate e del ciclo di eventi sono vuote?

Uscirà semplicemente.

Quando si esegue un programma Node, Node avvia automaticamente il loop degli eventi e quando quel loop degli eventi è inattivo e non ha nient'altro da fare, il processo termina.

Per mantenere in esecuzione un processo Node, è necessario posizionare qualcosa da qualche parte nelle code degli eventi. Ad esempio, quando si avvia un timer o un server HTTP, in pratica si dice al loop degli eventi di continuare a funzionare e controllare questi eventi.

Domanda n. 4: Oltre a V8 e Libuv, quali altre dipendenze esterne ha Node?

Di seguito sono riportate tutte le librerie separate che possono essere utilizzate da un processo Node:

  • http-parser
  • c-ares
  • OpenSSL
  • zlib

Tutti sono esterni al nodo. Hanno il loro codice sorgente. Hanno la loro licenza. Il nodo li usa e basta.

Lo vuoi ricordare perché vuoi sapere dove è in esecuzione il tuo programma. Se stai facendo qualcosa con la compressione dei dati, potresti riscontrare problemi in profondità nello stack della libreria zlib. Potresti combattere un bug zlib. Non dare la colpa a tutto su Node.

Domanda n. 5: sarebbe possibile eseguire un processo Node senza V8?

Questa potrebbe essere una domanda trabocchetto. È necessaria una VM per eseguire un processo Node, ma V8 non è l'unica VM che è possibile utilizzare. Puoi usare Chakra.

Dai un'occhiata a questo repository Github per tenere traccia dell'avanzamento del progetto nodo-chakra:

Domanda n. 6: qual è la differenza tra module.exports ed export?

Puoi sempre utilizzare module.exports per esportare l'API dei tuoi moduli. Puoi anche utilizzare le esportazioni tranne un caso:

module.exports.g = ... // Ok
exports.g = ... // Ok
module.exports = ... // Ok
export = ... // Non ok

Perché?

export è solo un riferimento o un alias in module.exports. Quando cambi le esportazioni stai cambiando quel riferimento e non cambiando più l'API ufficiale (che è module.exports). Otterresti semplicemente una variabile locale nell'ambito del modulo.

Domanda n. 7: come mai le variabili di livello superiore non sono globali?

Se hai module1 che definisce una variabile di livello superiore g:

// module1.js
var g = 42;

E hai module2 che richiede module1 e prova ad accedere alla variabile g, otterrai g non definito.

Perché? Se fai lo stesso in un browser, puoi accedere alle variabili definite di livello superiore in tutti gli script inclusi dopo la loro definizione.

Ogni file Node ottiene il proprio IIFE (Espressione di funzione immediatamente richiamata) dietro le quinte. Tutte le variabili dichiarate in un file Node sono incluse in quell'IIFE.

Domanda correlata: quale sarebbe l'output dell'esecuzione del seguente file Node che ha solo questa singola riga:

// script.js
console.log (argomenti);

Vedrai degli argomenti!

Perché?

Perché ciò che il nodo eseguito è una funzione. Il nodo ha racchiuso il tuo codice con una funzione e quella funzione definisce esplicitamente i cinque argomenti che vedi sopra.

Domanda n. 8: gli oggetti esportati, richiesti e il modulo sono tutti disponibili a livello globale in ogni file ma sono diversi in ogni file. Come?

Quando è necessario utilizzare l'oggetto request, è sufficiente utilizzarlo direttamente come se fosse una variabile globale. Tuttavia, se si ispeziona richiede in due file diversi, verranno visualizzati due oggetti diversi. Come?

A causa della stessa magia IIFE:

Come puoi vedere, il IIFE magico trasmette al tuo codice i seguenti cinque argomenti: export, request, module, __filename e __dirname.

Queste cinque variabili sembrano globali quando le usi in Nodo, ma in realtà sono solo argomenti di funzioni.

Domanda n. 9: quali sono le dipendenze dei moduli circolari in Node?

Se hai un module1 che richiede module2 e module2 che a sua volta richiede module1, che cosa accadrà? Un errore?

// modulo 1
require (' ./ Module2' );
// module2
require (' ./ Module1' );

Non otterrai un errore. Il nodo lo consente.

Quindi module1 richiede module2, ma poiché module2 ha bisogno di module1 e module1 non è ancora terminato, module1 otterrà solo una versione parziale di module2.

Sei stato avvertito.

Domanda n. 10: quando è corretto utilizzare il file system * Metodi di sincronizzazione (come readFileSync)

Ogni metodo fs in Node ha una versione sincrona. Perché dovresti usare un metodo di sincronizzazione anziché quello asincrono?

A volte l'uso del metodo di sincronizzazione va bene. Ad esempio, può essere utilizzato in qualsiasi passaggio di inizializzazione mentre il server sta ancora caricando. Accade spesso che tutto ciò che fai dopo la fase di inizializzazione dipenda dai dati che ottieni. Invece di introdurre un livello di richiamata, è accettabile utilizzare i metodi sincroni, purché ciò che si utilizza con i metodi sincroni sia una cosa singola.

Tuttavia, se si utilizzano metodi sincroni all'interno di un gestore come un callback su richiesta del server HTTP, sarebbe semplicemente sbagliato al 100%. Non farlo.

Spero che tu sia stato in grado di rispondere ad alcune oa tutte queste domande.

Grazie per aver letto.