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 ist0.arg1, arg2, ...— optionale Argumente, die direkt anfuncübergeben werden.
Der Rückgabewert ist eine numerische Timer-ID, die später an clearTimeout() übergeben werden kann.
Beispiel
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:
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
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.
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
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
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:
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 eine0-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 als0behandelt, wodurch die Ausführung fast sofort statt weit in der Zukunft erfolgt. this-Bindung. Wenn eine Methode wiesetTimeout(obj.method, 1000)übergeben wird, verliert sie ihrthis. Verwende eine Pfeilfunktion —setTimeout(() => obj.method(), 1000)— oderobj.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
- Introduction: callbacks — die Grundlage, auf der Timer aufbauen.
- The Event Loop: microtasks and macrotasks — warum ein
0-ms-Timeout trotzdem zuletzt ausgeführt wird. - Promise und Async/await — moderne Alternativen zur Sequenzierung asynchroner Aufgaben.
- Recursion and stack — Hintergrundwissen zum rekursiven
setTimeout-Muster.
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.