back to top

Node.js: l’oggetto global e alcuni moduli nativi

Nel corso di questa lezione faremo una rapida carrellata di alcuni dei moduli più interessanti di Node.js. L’API di Node.js è comunque vasta e ben documentata per cui, non potendo trattare tutti gli argomenti in maniera esaustiva, vi rimando alla documentazione ufficiale per maggiori dettagli e informazioni. Iniziamo a parlare dell’oggetto global e di alcuni dei moduli che abbiamo già incontrato nei precedenti articoli.

L’oggetto global

Nei precedenti articoli abbiamo già visto che alcuni moduli e oggetti sono disponibili globalmente in Node.js. Per esempio possiamo creare un oggetto di tipo Buffer senza dover importare il modulo corrispondente. Abbiamo anche visto che Node.js inserisce il codice scritto in un file con estensione.js all’interno di una funzione che consente di accedere alla funzione require(), alle variabili __dirname e __filename e all’oggetto module. La proprietà module.exports permette di stabilire quali metodi e variabili sono visibili all’esterno di un determinato modulo. In Node.js sono diponibili globalmente anche alcune utili funzioni come quelle elencate in basso:

  • le funzioni setInterval() e setTimeout() con cui probabilmente avete già familiarità se avete usato Javascript.
  • la funzione setImmediate() e clearImmediate() che possono essere usate per differire l’esecuzione di una certa funzione come abbiamo già visto nella lezione dedicato all’Event Loop.

Process

L’oggetto process è disponibile globalmente e mette a disposizione alcuni metodi e proprietà che possono risultare utili quando si sviluppa un’applicazione.

Process.stdin, Process.stdout e Process.stderr

Process.stdin, Process.stdout e Process.stderr sono delle proprietà dell’oggetto process (sono degli stream) che forniscono gli strumenti necessari per comunicare con il processo in esecuzione o con eventuali processi figli. Nei precedenti articoli abbiamo usato spesso process.stdout (attraverso il metodo console.log()) per stampare delle informazioni nel terminale. Possiamo usare process.stderr per stampare dei messaggi di errore mentre process.stdin è utile per ottenere l’input di un utente negli script eseguiti da linea di comando.

// app.js

process.stdin.on('data', (nome) => {
  process.stdout.write(`Ciao ${nome}`);
})

process.stdout.write('Inserisci il tuo nome: ');

Eseguendo node app.js, viene richiesto di inserire il nostro nome. Il prompt resta in attesa finché non inseriamo del testo e premiamo il tasto INVIO. A quel punto viene stampato un messaggio nel terminale come mostrato in basso.

$ node index.js

Inserisci il tuo nome: Claudio
Ciao Claudio

Notate che non viene terminata l’esecuzione. Possiamo infatti continuare a digitare dei caratteri nella shell.

Process.argv

La proprietà process.argv è un array contenente tutti gli argomenti passati da linea di comando al momento dell’esecuzione di un certo script.

node index.js --version -o a=1 b

[ 
  '/percorso/assoluto/node',
  '/percorso/assoluto/del/file/index.js',
  '--version',
  '-o',
  'a=1',
  'b' 
]

Process.pid

La proprietà process.pid restituisce il PID del processo corrente.

console.log(`PID del processo: ${process.pid}`); // es. 10161

Process.env

La proprietà process.env è particolarmente utile. Contiene le variabili di ambiente e può essere usata per eseguire una certa applicazione in diverse modalità. Vediamo un semplice esempio.

// index.js

const env = process.env.NODE_ENV || 'development';

if (env === 'production') {
  console.log('Buongiorno utente');
} else {
  console.log(`PID del processo: ${process.pid}`);
}

Se eseguiamo il comando riportato in basso, otteniamo l’output mostrato visto che abbiamo settato ‘NODE_ENV=production’.

NODE_ENV=production node index.js
Buongiorno utente

Process.cwd()

La funzione process.cwd() restituisce la directory corrente del processo Node.js in esecuzione e può risultare utile quando si realizzano degli script da eseguire da linea di comando.

Process.exit()

Useremo process.exit([codice]) per terminare un processo in maniera sincrona. Possiamo passare un valore intero come unico argomento per indicare se il processo ha completato la sua esecuzione con successo (codice 0) o meno. In quest’ultimo caso passeremo solitamente il valore 1. Se riprendiamo l’esempio visto sopra, possiamo modificarlo usando questa funzione.

process.stdin.on('data', (nome) => {
  nome = nome.toString().trim();
  if (nome !== '') {
    process.stdout.write(`Ciao ${nome}`);
    process.exit(0);
  } else {
    process.stderr.write('Nome non valido');
    process.exit(1);
  }
})

process.stdout.write('Inserisci il tuo nome: ');

Nell’esempio in alto, se non viene inserito un nome valido, viene stampato un messaggio di errore e il processo termina.

Eventi: exit e uncaughtException

Due eventi particolarmente utili emessi da process sono exit e uncaughtException. Quest’ultimo viene emesso nel caso venga lanciata un’eccezione che non viene gestita. Il primo invece viene emesso dopo aver invocato la funzione process.exit(). Riprendiamo e modifichiamo l’esempio appena visto.

process.on('uncaughtException', (errore) => {
  process.stderr.write(`\nCaught exception: ${errore}\n`);
  process.exit(1);
})

process.on('exit', (codiceTerminazione) => {
  process
    .stdout
    .write(
      'Il processo sta per essere terminato' + 
      'con codice di terminazione: ' + codiceTerminazione + '\n'
    );
})

process.stdin.on('data', (nome) => {
  nome = nome.toString().trim();
  if (nome !== '') {
    process.stdout.write(`Ciao ${nome}\n`);
    process.exit(0);
  } else {
    throw(new Error('Il nome inserito non è valido\n'));
  }
})

process.stdout.write('Inserisci il tuo nome: ');

Nel caso venga inserito un nome valido, terminiamo il processo correttamente. In caso contrario, lanciamo un’eccezione che verrà gestita dalla funzione registrata per l’evento uncaughtException. Quest’ultima stampa un messaggio di errore e termina l’esecuzione con codice pari a 1.

Console

Console è un altro oggetto disponibile globalmente con diversi i metodi che possiamo usare in fase di sviluppo. Abbiamo più volte usato console.log([data][, …args]) che stampa un messaggio sullo standard output (stdout). È possibile passare degli argomenti e usare una sintassi simile a quella della funzione printf() così come avviene in altri linguaggi.

console.log('Numero estratto: %d', Math.floor(100 * Math.random())) // es. Numero estratto: 73

Un altro metodo utile è console.time(label) che, usato in coppia con console.timeEnd(label), permette di misurare il tempo di esecuzione di una certa operazione;

console.time('tempo-esecuzione');
for (let i = 0; i < 1e10; i++) {}
console.timeEnd('tempo-esecuzione');

// es. tempo-esecuzione: 9058.034ms

Il modulo os

Anche il modulo os mette a disposizione un certo numero di metodi che possiamo usare in svariate situazioni. Fra i più utili riportiamo i seguenti:

  • os.cpus() che restituisce un array con le informazioni relative a ciascun processore/core installato;
  • os.totalmem() che restituisce la quantità totale di memoria installata in byte e può essere usata insieme a os.freemem() che fornisce invece il valore della memoria libera espresso in byte;
  • os.platform() che restituisce una stringa, identificativa del sistema operativo, settata da Node.js in fase di compilazione. (es. ‘linux’, ‘darwin’, ‘win32’, ‘freebsd’)

Il modulo path

Vediamo ora invece alcuni dei metodi e proprietà del modulo path.

Path.sep

Il valore di path.sep corrisponde al separatore di percorso specifico della piattaforma. (‘\’ per Windows. ‘/’ per LINUX e sistemi UNIX-like)

Path.dirname(percorso)

Il metodo path.dirname(percorso) restituisce invece il nome della directory di un certo percorso passato come argomento.

path.dirname('/Users/user/folder/something/') // Restituisce /Users/user/folder

Path.extname(percorso)

Il metodo path.extname(percorso) restituisce una stringa corrispondente all’estensione di un certo file.

path.extname('index.html'); // '.html'

path.extname('index.test.js'); // '.js'

path.extname('index.'); // '.'

Path.basename(percorso[, estensione])

Il metodo path.basename(percorso[, estensione]) restituisce la porzione finale di un certo percorso.

path.basename('/Users/user/folder/something/index.html');
// 'index.html'

path.basename('/Users/user/folder/something/index.html', '.html');
// 'index'

Path.parse(percorso)

Come suggerisce il nome stesso, il metodo path.parse(percorso) effettua il parsing del percorso passato come primo argomento e restituisce un oggetto contenente i dettagli dell’operazione eseguita.

path.parse('/Users/user/folder/something/index.html');

// { 
//   root: '/',
//   dir: '/Users/user/folder/something',
//   base: 'index.html',
//   ext: '.html',
//   name: 'index' 
// }

Path.join([…percorsi]) e Path.resolve([…percorsi])

Gli ultimi due metodi del modulo Path che vediamo sono path.join([…percorsi]) e path.resolve([…percorsi]) che accettano entrambi una serie di segmenti di percorsi, ma presentano alcune differenze.

Il metodo path.join([…percorsi]) unisce i vari segmenti passati come argomenti usando il separatore specifico della piattaforma.

path.join('/Users', 'user', 'dir/sub_dir', 'content', '..')
// '/Users/user/dir/sub_dir'

Il metodo path.resolve([…percorsi]) cerca di risolvere i vari segmenti passati come argomenti usando il separatore specifico della piattaforma. Elabora i segmenti passati da destra verso sinistra finché non viene formato un percorso assoluto. Se dopo aver elaborato tutti i segmenti, non si riesce a formare un percorso assoluto, viene usata la cartella corrente come directory base.

path.resolve('/Users', 'user', 'dir/sub_dir', 'content', '..')
// '/Users/user/dir/sub_dir'

path.resolve('/Users', '/user', 'dir/sub_dir', 'content', '..')
// '/user/dir/sub_dir'

path.resolve('Users', 'user', 'dir/sub_dir', 'content', '..')
// '/Users/claudio/Desktop/Users/user/dir/sub_dir'

Il modulo util

Per concludere questa lezione vediamo due metodi del modulo util.

util.debuglog(section)

Il metodo util.debuglog(section) può essere usato per stampare dei messaggi su stderr in base al valore della variabile d’ambiente NODE_DEBUG. Può essere usato in fase di debug per stampare nel terminale dei messaggi solo se viene eseguita l’applicazione con determinati valori di NODE_DEBUG.

NODE_DEBUG=test node debug.js

const util = require('util');
const debuglog = util.debuglog('test');

const NODE_DEBUG = process.env.NODE_DEBUG;

debuglog(`Stampa [ ${NODE_DEBUG} ] solo se (process.env.NODE_DEBUG === test) [ %s ]`, NODE_DEBUG);

// TEST 4287: Stampa [ test ] solo se (process.env.NODE_DEBUG === test) [ test ]

Nell’esempio appena visto, viene stampato un messaggio solo se il valore della variabile d’ambiente NODE_DEBUG è pari a ‘test’

util.promisify(original)

Al metodo util.promisify(original) passiamo il riferimento a una funzione che riceve a sua volta come ultimo argomento una funzione callback. (Si tratta essenzialmente delle funzioni impiegate per eseguire operazioni asincrone (per esempio fs.readFile(file, callback))). Il valore restituito da util.promisify(original) sarà una versione della funzione originale che restituisce però una Promise. In questo modo possiamo evitare di dover annidare una funzione dentro l’altra creando quella che viene spesso definita Callback Hell.

const util = require('util');
const fs = require('fs');

const file = 'quote.txt';

const readFile = util.promisify(fs.readFile);

readFile(file, 'utf8').then((quote) => {
  console.log(quote);
}).catch((error) => {
  console.error(`Si è verificato un errore in fase di lettura del file ${file}`);
});

Oppure usando il nuovo metodo delle funzioni async. (Per maggiori informazioni su async/await)

const util = require('util');
const fs = require('fs');

const file = 'quote.txt';

const readFile = util.promisify(fs.readFile);

async function execReadFile(file) {
  try {
    const quote = await readFile(file, 'utf8');
    console.log(quote);
  } catch (error) {
    console.error(`Si è verificato un errore in fase di lettura del file ${file}`);
  }
}

execReadFile(file);

Conclusioni

Nella prossima lezione useremo il modulo net e creeremo una semplice chat usando il protocollo TCP.

Pubblicitร