Come funziona Event Loop in JavaScript?

Sebbene possa essere necessaria una conoscenza approfondita di linguaggi come C++ e C per scrivere codice di produzione su vasta scala, JavaScript può spesso essere scritto con solo una conoscenza di base di cosa si può fare con il linguaggio.

Concetti, come il passaggio di callback alle funzioni o la scrittura di codice asincrono, spesso non sono così difficili da implementare, il che rende la maggior parte degli sviluppatori JavaScript meno interessati a ciò che accade sotto il cofano. Semplicemente non si preoccupano di comprendere le complessità che sono state profondamente astratte da loro dalla lingua.

Come sviluppatore JavaScript, diventa sempre più importante capire cosa succede realmente sotto il cofano e come funziona davvero la maggior parte di queste complessità astratte da noi. Ci aiuta a prendere decisioni più informate, che a loro volta possono aumentare drasticamente le prestazioni del nostro codice.

Questo articolo si concentra su uno dei concetti o termini molto importanti ma raramente compresi in JavaScript. L’EVENTO LOOP!.

La scrittura di codice asincrono non può essere evitata in JavaScript, ma perché un codice eseguito in modo asincrono significa davvero? cioè il ciclo di eventi

Prima di poter capire come funziona il ciclo di eventi, dobbiamo prima capire cos’è JavaScript stesso e come funziona!

Cos’è JavaScript?

Prima di procedere, vorrei che facessimo un passo indietro alle basi. Cos’è veramente JavaScript? Potremmo definire JavaScript come;

JavaScript è un linguaggio di alto livello, interpretato, a thread singolo, non bloccante, asincrono e concorrente.

Aspetta, cos’è questo? Una definizione libresca? 🤔

Analizziamolo!

Le parole chiave qui per quanto riguarda questo articolo sono thread singolo, non bloccante, simultaneo e asincrono.

Filo singolo

Un thread di esecuzione è la più piccola sequenza di istruzione programmata che può essere gestita autonomamente da uno scheduler. Un linguaggio di programmazione è a thread singolo significa che può eseguire solo un’attività o un’operazione alla volta. Ciò significa che eseguirà un intero processo dall’inizio alla fine senza che il thread venga interrotto o interrotto.

A differenza dei linguaggi multi-thread in cui più processi possono essere eseguiti contemporaneamente su più thread senza bloccarsi a vicenda.

In che modo JavaScript può essere a thread singolo e non bloccante allo stesso tempo?

Ma cosa significa bloccare?

Non bloccante

Non esiste una definizione di blocco; significa semplicemente cose che stanno funzionando lentamente sul thread. Quindi non bloccante significa cose che non sono lente sul thread.

Ma aspetta, ho detto che JavaScript viene eseguito su un singolo thread? E l’ho anche detto non bloccante, il che significa che l’attività viene eseguita rapidamente nello stack di chiamate? Ma come??? Che ne dici di quando eseguiamo i timer? Loop?

Rilassare! Lo scopriremo tra poco 😉.

Concorrente

Concorrenza significa che il codice viene eseguito contemporaneamente da più di un thread.

Ok, le cose stanno diventando davvero strano ora, come può JavaScript essere a thread singolo ed essere concorrente allo stesso tempo? cioè, eseguendo il suo codice con più di un thread?

Asincrono

Programmazione asincrona significa che il codice viene eseguito in un ciclo di eventi. Quando c’è un’operazione di blocco, l’evento viene avviato. Il codice di blocco continua a funzionare senza bloccare il thread di esecuzione principale. Quando il codice di blocco termina l’esecuzione, mette in coda il risultato delle operazioni di blocco e le reinserisce nello stack.

Ma JavaScript ha un singolo thread? Cosa esegue quindi questo codice di blocco mentre consente l’esecuzione di altri codici nel thread?

Prima di procedere, facciamo un riepilogo di quanto sopra.

  • JavaScript è a thread singolo
  • JavaScript non è bloccante, cioè i processi lenti non ne bloccano l’esecuzione
  • JavaScript è concorrente, cioè esegue il suo codice in più di un thread contemporaneamente
  • JavaScript è asincrono, cioè esegue il codice di blocco da qualche altra parte.

Ma quanto sopra non si somma esattamente, come può un linguaggio a thread singolo essere non bloccante, simultaneo e asincrono?

Andiamo un po’ più a fondo, scendiamo ai motori di runtime JavaScript, V8, forse ha alcuni thread nascosti di cui non siamo a conoscenza.

Motore V8

Il motore V8 è un motore runtime di assemblaggio web open source ad alte prestazioni per JavaScript scritto in C++ da Google. La maggior parte dei browser esegue JavaScript utilizzando il motore V8 e anche il popolare ambiente di runtime node js lo utilizza.

In parole povere, il V8 è un programma C++, che riceve il codice JavaScript, lo compila e lo esegue.

Il V8 fa due cose principali;

  • Allocazione della memoria dell’heap
  • Contesto di esecuzione dello stack di chiamate

Purtroppo, il nostro sospetto era sbagliato. Il V8 ha un solo stack di chiamate, pensa allo stack di chiamate come al thread.

Un thread === uno stack di chiamate === un’esecuzione alla volta.

Immagine – Mezzogiorno hacker

Poiché V8 ha un solo stack di chiamate, in che modo JavaScript viene eseguito contemporaneamente e in modo asincrono senza bloccare il thread di esecuzione principale?

Proviamo a scoprirlo scrivendo un semplice ma comune codice asincrono e analizziamolo insieme.

JavaScript esegue ogni codice riga per riga, uno dopo l’altro (a thread singolo). Come previsto, la prima riga viene stampata qui nella console, ma perché l’ultima riga viene stampata prima del codice di timeout? Perché il processo di esecuzione non attende il codice di timeout (blocco) prima di procedere per eseguire l’ultima riga?

Qualche altro thread sembra averci aiutato a eseguire quel timeout poiché siamo abbastanza sicuri che un thread possa eseguire solo una singola attività in qualsiasi momento.

Diamo una sbirciatina al Codice sorgente V8 per un po.

Aspetta cosa??!!! Non ci sono funzioni timer in V8, nessun DOM? Nessun evento? Niente AJAX?…. Siiiiii!!!

Eventi, DOM, timer, ecc. Non fanno parte dell’implementazione principale di JavaScript, JavaScript è rigorosamente conforme alle specifiche Ecma Scripts e varie versioni di esso sono spesso indicate in base alle sue specifiche Ecma Scripts (ES X).

Flusso di lavoro di esecuzione

Eventi, timer, richieste Ajax sono tutti forniti sul lato client dai browser e sono spesso indicati come Web API. Sono quelli che consentono al JavaScript a thread singolo di essere non bloccante, simultaneo e asincrono! Ma come?

Esistono tre sezioni principali nel flusso di lavoro di esecuzione di qualsiasi programma JavaScript, lo stack di chiamate, l’API Web e la coda delle attività.

Lo stack di chiamate

Una pila è una struttura di dati in cui l’ultimo elemento aggiunto è sempre il primo ad essere rimosso dalla pila, si potrebbe pensare ad essa come una pila di un piatto in cui solo il primo piatto che è stato l’ultimo aggiunto può essere rimosso per primo. Uno stack di chiamate non è semplicemente altro che una struttura di dati dello stack in cui le attività o il codice vengono eseguiti di conseguenza.

Consideriamo l’esempio seguente;

Fonte – https://youtu.be/8aGhZQkoFbQ

Quando chiami la funzione printSquare() , viene inserito nello stack di chiamate, la funzione printSquare() chiama la funzione square(). La funzione square() viene inserita nello stack e chiama anche la funzione multiply(). La funzione di moltiplicazione viene inserita nello stack. Poiché la funzione moltiplica ritorna ed è l’ultima cosa che è stata inserita nello stack, viene risolta per prima e viene rimossa dallo stack, seguita dalla funzione square() e quindi dalla funzione printSquare().

L’API Web

È qui che viene eseguito il codice che non è gestito dal motore V8 per non “bloccare” il thread di esecuzione principale. Quando lo stack di chiamate incontra una funzione API Web, il processo viene immediatamente trasferito all’API Web, dove viene eseguito e liberando lo stack di chiamate per eseguire altre operazioni durante la sua esecuzione.

Torniamo al nostro esempio setTimeout sopra;

Quando eseguiamo il codice, la prima riga console.log viene inserita nello stack e otteniamo il nostro output quasi immediatamente, al raggiungimento del timeout, i timer sono gestiti dal browser e non fanno parte dell’implementazione principale di V8, viene inviato all’API Web invece, liberando lo stack in modo che possa eseguire altre operazioni.

Mentre il timeout è ancora in esecuzione, lo stack passa alla riga di azione successiva ed esegue l’ultimo console.log, il che spiega perché viene emesso prima dell’output del timer. Una volta che il timer è terminato, succede qualcosa. Il console.log in poi il timer appare magicamente di nuovo nello stack di chiamate!

Come?

Il ciclo degli eventi

Prima di discutere il ciclo degli eventi, esaminiamo innanzitutto la funzione della coda delle attività.

Tornando al nostro esempio di timeout, una volta che l’API Web termina l’esecuzione dell’attività, non la rimanda automaticamente allo stack di chiamate. Va alla coda delle attività.

Una coda è una struttura di dati che funziona secondo il principio First in First out, quindi quando le attività vengono inserite nella coda, escono nello stesso ordine. Le attività che sono state eseguite dalle API Web, che vengono inviate alla coda delle attività, quindi tornano allo stack di chiamate per ottenere la stampa del risultato.

Ma aspetta. CHE DIAVOLO È L’EVENT LOOP???

Fonte – https://youtu.be/8aGhZQkoFbQ

Il ciclo di eventi è un processo che attende che lo stack di chiamate sia cancellato prima di inviare i callback dalla coda delle attività allo stack di chiamate. Una volta che lo stack è stato svuotato, il ciclo di eventi si attiva e controlla la coda delle attività per i callback disponibili. Se ce ne sono, lo inserisce nello stack di chiamate, attende che lo stack di chiamate sia nuovamente libero e ripete lo stesso processo.

Fonte – https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell

Il diagramma sopra mostra il flusso di lavoro di base tra il ciclo di eventi e la coda delle attività.

Conclusione

Sebbene questa sia un’introduzione molto semplice, il concetto di programmazione asincrona in JavaScript fornisce informazioni sufficienti per comprendere chiaramente cosa sta succedendo sotto il cofano e come JavaScript è in grado di funzionare contemporaneamente e in modo asincrono con un solo thread.

JavaScript è sempre on-demand e, se sei curioso di imparare, ti consiglio di dare un’occhiata a questo Corso Udem.