Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Event loop e libuv in Node.js: evitare le cattive pratiche

Programmare applicazioni concorrenti con Node.js, sfruttando le promise e il costrutto async/await per evitare il callback hell.
Programmare applicazioni concorrenti con Node.js, sfruttando le promise e il costrutto async/await per evitare il callback hell.
Link copiato negli appunti

Com'è noto, JavaScript opera su un singolo thread. Node.js utilizza la libreria
libuv per sfruttare questa
caratteristica e gestire l’esecuzione attraverso l’event loop, che
consente di eseguire operazioni che non bloccano il flusso di I/O.

La caratteristica principale di libuv che permette a Node.js di supportare
l’esecuzione asincrona di JavaScript, è sostanzialmente la sua gestione
degli eventi delle principali operazioni di I/O del kernel, come i socket
TCP e UDP, la risoluzione DNS, le operazioni sul file system e sui file, e
gli eventi del file system stesso.

In libuv tutte queste operazioni sono asincrone, ma l’aspetto più
importante di questa libreria è che tramite epoll, kqueue, IOCP ed eventi
fornisce a Node.js le fondamenta per costruire il suo event loop.

La

documentazione sull’Event Loop

illustra le fasi di questo ciclo, ma rivela anche un problema insito nel
design delle API di Node.js: accanto a metodi tradizionalmente asincroni,
esistono infatti anche metodi sincroni che, di fatto, bloccano l’event loop in una determinata fase, impedendogli
di proseguire nell’esecuzione delle fasi successive.

Un tipico esempio è il metodo del modulo core fs, che gestisce
le operazioni sui file e sul file system. Fintanto che i contenuti di un
file sono già predeterminati e le sue dimensioni sono fisse, note e
prestabilite (come nel caso della lettura di una chiave privata e di un
certificato SSL), questo metodo sincrono non ha un impatto negativo
sull’event loop. Al contrario, e soprattutto quando la lettura sincrona
viene esposta nel frontend tramite HTTP/S (come nel caso delle immagini),
si possono avere gravi ripercussioni sulla performance fino alla messa in
atto di un attacco DOS che non solo blocca l’esecuzione di Node, ma va anche
ad impattare sulla performance globale del server che ospita il sito o
l’app.

Per evitare il callback hell insito nella sintassi
stessa dei metodi asincroni, si possono adottare le caratteristiche più recenti di
ECMAScript, come le Promise e il costrutto async/await che mantengono
inalterata l’asincronicità delle operazioni fornendo però una sintassi che
ci permette di evitare l’annidamento di funzioni su funzioni.

Ad esempio, possiamo creare la seguente classe di utility che fa uso delle
Promise:

'use strict';
const path = require('path');
const fs = require('fs');
const ABSPATH = path.dirname(process.mainModule.filename);
class Files {
    static read(path, encoding = 'utf8') {
      return new Promise((resolve, reject) => {
        let readStream = fs.createReadStream(ABSPATH + path, encoding);
        let data = '';
        readStream.on('data', chunk => {
            data += chunk;
        }).on('end', () => {
            resolve(data);
        }).on('error', err => {
            reject(err);
        });
      });
    }
    static create(path, contents) {
        return new Promise((resolve, reject) => {
            fs.writeFile(ABSPATH + path, contents, (err, data) => {
                if(!err) {
                    resolve(data);
                } else {
                    reject(err);
                }
            });
        });
    }
    static remove(path) {
        return new Promise((resolve, reject) => {
            fs.unlink(ABSPATH + path, err => {
                if(!err) {
                    resolve(path);
                } else {
                    reject(err);
                }
            });
        });
    }
    static exists(path) {
        return new Promise((resolve, reject) => {
            fs.access(ABSPATH + path, fs.constants.F_OK, err => {
               if(!err) {
                   resolve(true);
               } else {
                   reject(err);
               }
            });
        });
    }
}
module.exports = Files;

Quindi possiamo coniugare il modello delle Promise con il costrutto async/await:

'use strict';
const app = require('expresss')();
const Files = require('./lib/Files');
app.get('/api/file', async (req, res) => {
    try {
        let data = await Files.read('/log/file.log');
        res.send(data);
    } catch(err) {
        res.sendStatus(500);
    }
});
app.listen(8000);

Come si può notare, tramite l’uso combinato delle Promise e del costrutto async/await abbiamo evitato il callback hell anche grazie ad un'attenta separazione e delegazione dei compiti nel nostro codice.

Conclusione

Abbiamo visto come l’event loop di Node.js sia basato su operazioni asincrone e come tale asincronicità vada preservata in modo da non interrompere lo stesso event loop.

Ti consigliamo anche