W3docs

JavaScript setTimeout und setInterval

JavaScript-Code zeitgesteuert ausführen: setTimeout, setInterval, Argumente übergeben, Timer abbrechen, rekursives setTimeout, Zero-Delay, Debouncing und Throttling.

Manchmal möchte man Code nicht sofort ausführen — sondern später oder wiederholt. Die beiden Planungsfunktionen von JavaScript, setTimeout() und setInterval(), ermöglichen genau das. Dieser Leitfaden behandelt ihre Syntax, die Übergabe von Argumenten, das Abbrechen geplanter Timer, das wichtige Muster des „rekursiven setTimeout", das überraschende Verhalten einer Null-Verzögerung sowie zwei praxisnahe Anwendungsfälle: Debouncing und Throttling.

Keine dieser Funktionen ist Teil der JavaScript-Kernsprache — sie werden von der Host-Umgebung (Browsern und Node.js) bereitgestellt. Das hier beschriebene Verhalten ist in beiden gleich, mit einigen Unterschieden, die an den entsprechenden Stellen erwähnt werden.

Einführung in JavaScript-Timing-Funktionen

Ein Timer plant einen Callback, der ausgeführt wird, nachdem der aktuelle Code abgeschlossen ist und eine bestimmte Zeit vergangen ist. Da JavaScript single-threaded ist, unterbricht der Callback niemals laufenden Code; er wartet in einer Warteschlange und wird erst ausgeführt, wenn der Call Stack leer ist. (Den vollständigen Mechanismus erklärt The Event Loop.)

Die Funktion setTimeout()

setTimeout() führt eine Funktion einmalig nach einer angegebenen Verzögerung aus. Sie akzeptiert eine auszuführende Funktion und eine Verzögerung in Millisekunden vor dieser Ausführung.

Syntax

let timerId = setTimeout(func, delay, arg1, arg2, ...);
  • func — die auszuführende Funktion (oder, seltener, ein Code-String).
  • delay — die Wartezeit in Millisekunden vor der Ausführung. Standardwert ist 0.
  • arg1, arg2, ... — optionale Argumente, die direkt an func übergeben werden.

Der Rückgabewert ist eine numerische Timer-ID, die später an clearTimeout() übergeben werden kann.

Beispiel

javascript— editable

Argumente an den Callback übergeben

Alles, was nach der Verzögerung angegeben wird, wird an den Callback weitergeleitet. Das ist übersichtlicher als das Einwickeln des Aufrufs in eine weitere Pfeilfunktion:

javascript— editable
Warnung
Übergebe die Funktion selbst, nicht ihr Ergebnis. setTimeout(greet(), 1000) führt greet() sofort aus und plant dessen Rückgabewert (wahrscheinlich undefined). Schreibe setTimeout(greet, 1000) — ohne Klammern.

Die Funktion setInterval()

setInterval() hat dieselbe Signatur wie setTimeout(), aber anstatt den Callback einmal auszuführen, führt es ihn wiederholt alle delay Millisekunden aus, bis er gestoppt wird.

Syntax

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

Beispiel

javascript— editable
Info
Wenn der Callback länger braucht als das Intervall, können sich Aufrufe stapeln und die tatsächliche Zeitspanne driftet. Der Browser garantiert außerdem eine Mindestlücke zwischen Callbacks, sodass aufeinanderfolgende Ausführungen näher am nächsten Tick liegen können als erwartet. Wenn präzise Abstände wichtig sind, ist das unten beschriebene rekursive setTimeout-Muster vorzuziehen.

Rekursives setTimeout vs. setInterval

Man kann setInterval() nachbilden, indem ein setTimeout()-Callback sich selbst neu plant. Der wesentliche Unterschied: setInterval() misst die Verzögerung zwischen Starts, während rekursives setTimeout() sie zwischen dem Ende eines Durchlaufs und dem Start des nächsten misst — und so eine feste Pause garantiert, selbst wenn der Callback langsam ist.

javascript— editable

Geplante Ausführung abbrechen

Beide Funktionen geben eine Timer-ID zurück. Wer diese ID aufbewahrt, kann die geplante Aufgabe mit clearTimeout() oder clearInterval() abbrechen. (Die beiden Clear-Funktionen sind in den meisten Engines tatsächlich austauschbar, aber das Zuordnen zur erzeugenden Planungsfunktion macht den Code lesbarer.)

setTimeout() stoppen

Um einen Timeout abzubrechen, speichert man die von setTimeout() zurückgegebene ID und übergibt sie an clearTimeout(), bevor die Verzögerung abläuft.

Beispiel

javascript— editable

setInterval() stoppen

Ebenso speichert man die ID von setInterval() und übergibt sie an clearInterval(). Ohne dies läuft das Intervall für immer (oder bis die Seite geschlossen wird), was eine häufige Quelle von Speicherlecks und unkontrollierten Timern ist.

Beispiel

javascript— editable

Das Zero-Delay-setTimeout

setTimeout(func, 0) führt func nicht sofort aus. Es plant func so, dass es ausgeführt wird, sobald der aktuelle synchrone Code abgeschlossen ist. Das ist eine praktische Möglichkeit, „nachzugeben" — dem Browser Zeit zum Neuzeichnen zu geben oder eine lange Aufgabe in Teile aufzuteilen — und es erklärt eine Ausgabereihenfolge, die Einsteiger oft überrascht:

javascript— editable

Beachte, dass Timer Macrotasks sind: Sie werden ausgeführt, nachdem alle in der Warteschlange stehenden Microtasks (wie aufgelöste Promise-Callbacks) abgearbeitet wurden. Die genauen Reihenfolgeregeln beschreibt Event loop: microtasks and macrotasks.

Praktische Anwendungen und Tipps

Zwei der häufigsten Praxisanwendungen von setTimeout() sind Debouncing und Throttling — beides Methoden, um zu begrenzen, wie oft eine Funktion auf schnell aufeinanderfolgende Ereignisse reagiert.

Debouncing mit setTimeout()

Debouncing wartet, bis ein Ereignis-Burst aufgehört hat, bevor die Funktion ausgeführt wird. Jedes neue Ereignis setzt den Timer zurück, sodass der Callback erst ausgelöst wird, nachdem für wait Millisekunden Ruhe eingekehrt ist. Das ist ideal für ein Suchfeld: Man möchte eine Anfrage nach dem Ende des Tippens senden, nicht bei jedem Tastendruck.

<!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 mit setTimeout()

Throttling führt die Funktion höchstens einmal pro limit Millisekunden aus, unabhängig davon, wie viele Ereignisse in der Zwischenzeit eintreffen. Während Debouncing auf Stille wartet, garantiert Throttling einen gleichmäßigen Rhythmus — perfekt für scroll-, resize- oder mousemove-Handler, die sonst dutzende Male pro Sekunde feuern würden. Das folgende Beispiel verwendet einen leading-edge-Ansatz (es wird sofort beim ersten Ereignis ausgeführt und erzwingt dann den Abstand):

<!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>

Häufige Fallstricke

  • Verzögerungen sind Mindestwerte, keine Garantien. Wenn der Call Stack beschäftigt oder der Event Loop überlastet ist, wartet der Callback. Der angegebene Wert ist der frühestmögliche Ausführungszeitpunkt, keine Zusicherung.
  • Hintergrund-Tabs werden gedrosselt. Die meisten Browser begrenzen Timer in inaktiven Tabs auf etwa einmal pro Sekunde, um Energie zu sparen, sodass Animationen und Polling verlangsamt werden, wenn der Tab versteckt ist.
  • Verschachtelte Timeouts werden auf ~4ms begrenzt. Nach fünf verschachtelten setTimeout()-Aufrufen erzwingen Browser eine Mindest-Verzögerung von etwa 4 Millisekunden, sodass eine 0-Verzögerung in tief verschachtelten Ketten nie wirklich null ist.
  • Maximale Verzögerung. Eine Verzögerung größer als 2147483647 (2^31 − 1) läuft über das 32-Bit-Feld über und wird als 0 behandelt, wodurch die Ausführung fast sofort statt weit in der Zukunft erfolgt.
  • this-Bindung. Wenn eine Methode wie setTimeout(obj.method, 1000) übergeben wird, verliert sie ihr this. Verwende eine Pfeilfunktion — setTimeout(() => obj.method(), 1000) — oder obj.method.bind(obj).
  • Immer aufräumen. Lösche Intervalle (und ausstehende Timeouts), wenn eine Komponente ausgehängt wird oder die Arbeit nicht mehr benötigt wird, sonst entstehen Timer-Lecks und es kann mit veraltetem Zustand gearbeitet werden.

Verwandte Themen

Fazit

setTimeout() führt Code einmalig nach einer Verzögerung aus; setInterval() führt ihn wiederholt gemäß einem Zeitplan aus; clearTimeout() und clearInterval() brechen sie ab. Denke daran, dass Verzögerungen Mindestwerte sind, übergib Argumente nach der Verzögerung statt innerhalb einer Wrapper-Funktion, greife auf das rekursive setTimeout-Muster zurück, wenn gleichmäßige Abstände benötigt werden, und räume Timer immer auf, wenn sie nicht mehr gebraucht werden. Zusammen mit Debouncing und Throttling decken diese beiden kleinen Funktionen den Großteil der zeitgesteuerten Arbeit im Browser ab.

Übungen

Übung
Welche der folgenden Aussagen über den Einsatz von `setTimeout()` und `setInterval()` in JavaScript sind richtig?
Welche der folgenden Aussagen über den Einsatz von `setTimeout()` und `setInterval()` in JavaScript sind richtig?
Was this page helpful?