W3docs

JavaScript setTimeout e setInterval

Impara a pianificare il codice in JavaScript con setTimeout e setInterval: sintassi, argomenti, annullamento timer, pattern ricorsivo, zero-delay, debouncing e throttling.

A volte non si vuole eseguire del codice subito — lo si vuole eseguire dopo, oppure ripetutamente. Le due funzioni di pianificazione di JavaScript, setTimeout() e setInterval(), permettono di fare esattamente questo. Questa guida ne illustra la sintassi, come passare argomenti, come annullare un timer pianificato, il fondamentale pattern del "setTimeout ricorsivo", il comportamento sorprendente di un ritardo pari a zero e due utilizzi pratici: il debouncing e il throttling.

Nessuna delle due funzioni fa parte del linguaggio JavaScript principale — sono fornite dall'ambiente host (browser e Node.js). Il comportamento descritto qui è lo stesso in entrambi, con alcune differenze indicate lungo il percorso.

Introduzione alle funzioni di temporizzazione in JavaScript

Un timer pianifica l'esecuzione di una callback dopo che il codice corrente ha terminato e trascorso un determinato intervallo di tempo. Poiché JavaScript è single-threaded, la callback non interrompe mai il codice in esecuzione; attende in una coda e viene eseguita solo quando lo stack di chiamate è vuoto. (Per il meccanismo completo, vedere The Event Loop.)

La funzione setTimeout()

setTimeout() esegue una funzione una volta dopo un ritardo specificato. Accetta una funzione da eseguire e un ritardo in millisecondi prima di quell'esecuzione.

Sintassi

let timerId = setTimeout(func, delay, arg1, arg2, ...);
  • func — la funzione (o, meno comunemente, una stringa di codice) da eseguire.
  • delay — l'attesa in millisecondi prima dell'esecuzione. Il valore predefinito è 0.
  • arg1, arg2, ... — argomenti opzionali passati direttamente a func.

Il valore restituito è un id numerico del timer che è possibile passare in seguito a clearTimeout().

Esempio

javascript— editable

Passare argomenti alla callback

Tutto ciò che si inserisce dopo il ritardo viene inoltrato alla callback. Questo è più pulito che racchiudere la chiamata in un'altra funzione freccia:

javascript— editable
Attenzione
Passare la funzione stessa, non il suo risultato. setTimeout(greet(), 1000) esegue greet() immediatamente e pianifica il suo valore di ritorno (probabilmente undefined). Scrivere setTimeout(greet, 1000) — senza parentesi.

La funzione setInterval()

setInterval() ha la stessa firma di setTimeout(), ma invece di eseguire la callback una sola volta, la esegue ripetutamente ogni delay millisecondi finché non viene interrotta.

Sintassi

let timerId = setInterval(func, delay, arg1, arg2, ...);

Esempio

javascript— editable
Informazione
Se la callback impiega più tempo a girare rispetto all'intervallo, le chiamate possono accumularsi e la spaziatura reale deriva. Il browser garantisce anche un gap minimo tra le callback, quindi esecuzioni ravvicinate possono essere più vicine al tick successivo di quanto ci si aspetti. Quando la spaziatura precisa è importante, preferire il pattern setTimeout ricorsivo descritto di seguito.

setTimeout ricorsivo vs. setInterval

È possibile riprodurre setInterval() facendo sì che la callback di un setTimeout() ripianifichi se stessa. La differenza fondamentale: setInterval() misura il ritardo tra gli avvii, mentre setTimeout() ricorsivo lo misura tra la fine di un'esecuzione e l'inizio della successiva — garantendo una pausa fissa anche quando la callback è lenta.

javascript— editable

Annullamento dell'esecuzione pianificata

Entrambe le funzioni restituiscono un id del timer. Conservando quell'id è possibile annullare il lavoro pianificato con clearTimeout() o clearInterval(). (Le due funzioni di cancellazione sono in realtà intercambiabili nella maggior parte dei motori, ma abbinarle allo scheduler che ha creato l'id rende il codice più leggibile.)

Interrompere setTimeout()

Per annullare un timeout, memorizzare l'id restituito da setTimeout() e passarlo a clearTimeout() prima che il ritardo sia trascorso.

Esempio

javascript— editable

Interrompere setInterval()

Analogamente, salvare l'id di setInterval() e passarlo a clearInterval(). Senza questo, l'intervallo gira all'infinito (o fino alla chiusura della pagina), il che è una fonte comune di memory leak e timer fuori controllo.

Esempio

javascript— editable

Il setTimeout con ritardo zero

setTimeout(func, 0) non esegue func immediatamente. Pianifica l'esecuzione di func non appena il codice sincrono corrente ha terminato. Questo è un modo pratico per "cedere il controllo" — per consentire al browser di ridisegnare o per suddividere un'attività lunga in pezzi — e spiega l'ordine di output che spesso sorprende i principianti:

javascript— editable

Si noti che i timer sono macrotask: vengono eseguiti dopo tutti i microtask in coda (come le callback delle promise risolte). Vedere Event loop: microtask e macrotask per le regole di ordinamento precise.

Applicazioni pratiche e consigli

Due degli utilizzi pratici più comuni di setTimeout() sono il debouncing e il throttling — entrambi sono modi per limitare la frequenza con cui una funzione viene eseguita in risposta a eventi rapidi e ripetuti.

Debouncing con setTimeout()

Il debouncing attende che una serie di eventi si sia arrestata prima di eseguire la funzione. Ogni nuovo evento reimposta il timer, quindi la callback si attiva solo dopo che le cose si sono calmate per wait millisecondi. Questo è ideale per una casella di ricerca: si vuole inviare una richiesta dopo che l'utente ha smesso di digitare, non una per ogni tasto premuto.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounced Input Example</title>
<script>
    // Debounce function to limit the rate at which a function is executed
    function debounce(func, wait) {
        let timeout;

        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };

            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Function to be debounced
    function fetchData(input) {
        alert(`API call with input: ${input}`); // Placeholder for an API call
    }

    // Create a debounced version of fetchData
    const debouncedFetchData = debounce(fetchData, 300);

    // Add the debounced function to an event listener
    function setup() {
        document.getElementById('searchInput').addEventListener('input', (event) => {
            debouncedFetchData(event.target.value);
        });
    }

    // Ensure setup is called once the document is fully loaded
    document.addEventListener('DOMContentLoaded', setup);
</script>
</head>
<body>
    <h3>Type in the input field:</h3>
    <input type="text" id="searchInput" placeholder="Start typing..." />
</body>
</html>

Throttling con setTimeout()

Il throttling esegue la funzione al massimo una volta ogni limit millisecondi, indipendentemente da quanti eventi arrivano nel frattempo. Dove il debouncing attende il silenzio, il throttling garantisce un ritmo costante — perfetto per i gestori di scroll, resize o mousemove che altrimenti si attiverebbero decine di volte al secondo. L'esempio seguente utilizza un approccio leading-edge (viene eseguito immediatamente al primo evento, poi impone il gap):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Throttled Scroll Event</title>
<style>
  /* Simple styling for demonstration */
  body, html {
    height: 200%; /* Make the page scrollable */
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
  }
  #log {
    position: fixed;
    top: 0;
    left: 0;
    background: white;
    border: 1px solid #ccc;
    padding: 10px;
    width: 300px;
  }
</style>
</head>
<body>
<div id="log">Scroll to see the effect...</div>
<script>
// Throttle function using setTimeout
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, Math.max(0, limit - (Date.now() - lastRan)));
    }
  }
}

// Function to be throttled
function handleScroll() {
  const log = document.getElementById('log');
  log.textContent = `Scroll event triggered at: ${new Date().toLocaleTimeString()}`;
}

// Add event listener for scroll
window.addEventListener('scroll', throttle(handleScroll, 1000));
</script>
</body>
</html>

Aspetti da tenere a mente

  • I ritardi sono un minimo, non una garanzia. Se lo stack di chiamate è occupato o l'event loop è congestionato, la callback attende. Il numero passato è il momento più presto in cui può essere eseguita, non una promessa.
  • Le schede in background vengono rallentate. La maggior parte dei browser limita i timer nelle schede inattive a circa una volta al secondo per risparmiare energia, quindi le animazioni e il polling rallentano quando la scheda è nascosta.
  • I timeout annidati vengono limitati a circa 4 ms. Dopo cinque chiamate setTimeout() annidate, i browser impongono un ritardo minimo di circa 4 millisecondi, quindi un ritardo 0 non è mai veramente zero in catene profonde.
  • Ritardo massimo. Un ritardo superiore a 2147483647 (2^31 − 1) va in overflow nel campo a 32 bit e viene trattato come 0, scattando quasi immediatamente invece che in un futuro lontano.
  • Binding di this. Quando si passa un metodo come setTimeout(obj.method, 1000), questo perde il suo this. Usare una funzione freccia — setTimeout(() => obj.method(), 1000) — oppure obj.method.bind(obj).
  • Pulire sempre. Eliminare gli intervalli (e i timeout pendenti) quando un componente viene smontato o il lavoro non è più necessario, altrimenti si creeranno memory leak e si potrebbe operare su uno stato obsoleto.

Argomenti correlati

Conclusione

setTimeout() esegue il codice una volta dopo un ritardo; setInterval() lo esegue secondo una pianificazione ripetuta; clearTimeout() e clearInterval() li annullano. Ricordare che i ritardi sono minimi, passare gli argomenti dopo il ritardo anziché all'interno di un wrapper, ricorrere al pattern setTimeout ricorsivo quando si ha bisogno di una spaziatura costante e pulire sempre i timer non più necessari. Con il debouncing e il throttling in aggiunta, queste due piccole funzioni coprono la maggior parte del lavoro basato sul tempo che si esegue nel browser.

Esercizi

Pratica
Quali delle seguenti affermazioni sono vere riguardo all'uso di `setTimeout()` e `setInterval()` in JavaScript?
Quali delle seguenti affermazioni sono vere riguardo all'uso di `setTimeout()` e `setInterval()` in JavaScript?
Was this page helpful?