1.1 Concetti di base dell'asincronia
L'asincronia in JavaScript permette di eseguire attività in background, senza bloccare il flusso principale di esecuzione. Questo è particolarmente importante per compiti che potrebbero richiedere tempo significativo, come richieste di rete, lettura di file e timer. Ora daremo un'occhiata ai concetti di base della programmazione asincrona in JavaScript e forniremo esempi di utilizzo.
Natura single-thread di JavaScript
JavaScript è un linguaggio single-thread: ciò significa che esegue il codice in modo sequenziale su un singolo thread. Tuttavia, le operazioni asincrone permettono di eseguire compiti in background, liberando il flusso principale per altre attività.
Event Loop (Ciclo degli eventi)
Il ciclo degli eventi (Event Loop) è il meccanismo chiave che permette a JavaScript di gestire compiti asincroni. L'Event Loop gestisce la coda dei messaggi (Message Queue) e la coda delle microtasks (Microtask Queue), garantendo l'esecuzione delle operazioni asincrone.
- Coda dei messaggi: contiene compiti come gestori di eventi, richieste di rete e timer. I compiti di questa coda vengono eseguiti in sequenza.
- Coda delle microtasks: contiene compiti con priorità superiore rispetto a quelli nella coda dei messaggi. Gli esempi includono il completamento dei promises e le chiamate alle funzioni di callback nelle microtasks.
L'Event Loop controlla costantemente entrambe le code ed esegue i compiti da esse, quando il flusso principale diventa libero.
Operazioni asincrone
Le operazioni asincrone permettono di eseguire compiti in background. Gli esempi principali di operazioni asincrone includono:
- Timer (
setTimeout,setInterval) - Gestori di eventi
- Richieste di rete (es.,
XMLHttpRequest,Fetch API) - Lettura/scrittura file (in Node.js)
Diamo un'occhiata ad alcuni esempi di operazioni asincrone.
1.2 Timer
I timer permettono di eseguire compiti con un ritardo o a intervalli regolari di tempo.
Esempio di utilizzo di setTimeout
In questo esempio setTimeout imposta l'esecuzione di una funzione dopo 2 secondi. Come risultato, prima viene stampato Start e End, e poi dopo 2 secondi viene stampato Executed after 2 seconds.
console.log('Start');
setTimeout(() => {
console.log('Executed after 2 seconds');
}, 2000);
console.log('End');
Esempio di utilizzo di setInterval
In questo esempio setInterval esegue una funzione ogni secondo, incrementando il contatore e stampandone il valore. Quando il contatore raggiunge 5, l'intervallo viene pulito usando clearInterval:
let counter = 0;
const intervalID = setInterval(() => {
counter++;
console.log(`Counter: ${counter}`);
if (counter >= 5) {
clearInterval(intervalID);
}
}, 1000);
1.3 Gestori di eventi
I gestori di eventi permettono di eseguire codice in risposta ad azioni dell'utente o altri eventi.
Esempio di utilizzo di gestori di eventi
In questo esempio, un gestore dell'evento click viene aggiunto a un bottone. Quando l'utente clicca sul bottone, viene stampato il messaggio Button clicked!:
<!DOCTYPE html>
<html>
<head>
<title>Event Handler Example</title>
</head>
<body>
<button id="myButton">Click me</button>
<script>
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log('Button clicked!');
});
</script>
</body>
</html>
1.4 Richieste di rete
Le richieste di rete permettono di eseguire richieste HTTP asincrone al server.
Esempio di utilizzo di XMLHttpRequest
In questo esempio viene creato un GET request asincrono a un API, e quando la richiesta è completata, la risposta viene stampata in console:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://jsonplaceholder.typicode.com/posts/1', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
console.log(response);
}
};
xhr.send();
1.5 Event Loop in azione
Per capire meglio come funziona l'Event Loop, diamo un'occhiata al seguente esempio:
console.log('Start');
setTimeout(() => {
console.log('Timeout 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 1');
});
setTimeout(() => {
console.log('Timeout 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise 2');
});
console.log('End');
Il risultato atteso sarà il seguente:
StartEndPromise 1Promise 2Timeout 1Timeout 2
In questo esempio vengono eseguite prima le operazioni sincrone (console.log('Start') e console.log('End')). Poi vengono eseguite le microtasks (gestori dei promises), e solo dopo vengono eseguiti i compiti dalla coda dei messaggi (setTimeout).
GO TO FULL VERSION