JavaScript/WindowOrWorkerGlobalScope/setTimeout

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Die Methode setTimeout veranlasst JavaScript, nach Ablauf einer vorgegebenen Zeit eine Funktion aufzurufen. Im Gegensatz zu setInterval erfolgt der Funktionsruf aber nur ein einziges Mal.

Syntax 1 – ab ECMAScript 5 verfügbar

var timeoutID = window.setTimeout(funktion, zeitspanne, [parameter1, parameter2, ...]);

Diese Funktion erwartet zwei oder mehr Parameter:

  • funktion = Ein Funktionsobjekt, das nach Ablauf der genannten Zeitspanne ausgeführt werden soll.
  • zeitspanne = Wert in Millisekunden bis zum Ausführen der im 1. Parameter angegebenen Funktion.
  • weitere Parameter ... = optionale Argumente für das im ersten Parameter übergebene Funktionsobjekt (im Internet Explorer ab Version 10).
  • Rückgabewert: Die Timeout-Id (für clearTimeout())

Syntax 2

var timeoutID = window.setTimeout(string, zeitspanne);

Die zweite Aufrufvariante existiert aus historischen Gründen. Vermeiden Sie diese Variante. Der übergebene String wird wie ein eval ausgeführt, im globalen Kontext, mit allen Risiken und Nachteilen, die auch eval hat. Auch ältere Browser unterstützen die Syntax 1.

  • string = Eine Zeichenkette, die Javascript-Code enthält, der nach der genannten Zeitspanne ausgeführt werden soll.
  • zeitspanne = Wert in Millisekunden bis zum Ausführen der im 1. Parameter angegebenen Funktion.
  • Rückgabewert: Die Timeout-Id (für clearTimeout())

Funktionsweise

Beispiel ansehen …
function timeOut() {
  alert('Die Zeit ist um!');
  history.back();
}
setTimeout(timeOut, 3000);
Veraltete Nutzung mit einem String-Parameter
setTimeout("alert('Deine Zeit ist um!'); history.back();", 3000);

Drosselung

Ein setTimeout-Aufruf mit einer Wartezeit von 0 dient häufig dazu, Zwischenergebnisse anzeigen zu können. Veränderungen im DOM, die ein Script ausführt, werden erst nach Ende der Scriptausführung vom Browser visualisiert. Durch setTimeout(..., 0) kann man erreichen, dass das aktuelle Script endet, der Browser die Darstellung aktualisiert und danach den Folgeschritt aufruft.

Timeout-Ereignisse und Ereignisse aus Benutzerinteraktionen werden allerdings gleichberechtigt vom Browser verarbeitet. Um zu verhindern, dass eine Webseite durch setTimeout-Requests die Verarbeitung der Benutzerinteraktionen behindert, zählt der Browser mit, über wieviele setTimeout-Schritte eine Funktion aufgerufen wurde. Überschreitet diese Zahl eine bestimmte Schwelle, wird die Wartezeit auf einen Mindestwert gesetzt. Die HTML 5 Spezifikation legt die Schwelle auf 5 fest, und die Mindestwartezeit auf 4ms.

Wenn das Script auf einer Seite läuft, deren Browserfenster gerade im Hintergrund läuft (bzw. auf einem inaktiven Tab des Browsers), greift in Firefox und Chrome eine weitere Drosselung: Die Wartezeit wird hier auf mindestens 1000ms gesetzt, auf Mobilgeräten kann die Mindestwartezeit sogar auf mehrere Minuten steigen, um Akkulaufzeit zu sparen.

Übergabe von optionalen Parametern

In der ursprünglichen Definition der setTimeout Funktion war lediglich die Übergabe eines JavaScript-Codefragments vorgesehen, das zur Laufzeit übersetzt und bei Ablauf des Times ausgeführt wird. Eine Übergabe von Parametern an dieses Codefragment war und ist nicht vorgesehen.

Irgendwann vor 2010 kam die Möglichkeit hinzu, ein Funktionsobjekt zu übergeben. Weil Funktionen Parameter haben können, wurde auch die Möglichkeit spezifiziert, diese an setTimeout zu übergeben. Sie werden von setTimeout aufbewahrt und bei Ablauf des Timers an das Funktionsobjekt übergeben.

Beachten Sie: Der Internet Explorer hat lange Zeit JavaScript und VBScript als Scriptsprache unterstützt. Der String, den man als ersten Parameter übergeben kann, konnte JavasScript oder VBScript sein. Um mitteilen zu können, welche Sprache es ist, hat der Internet Explorer einen dritten Parameter für setTimeout festgelegt. Ab Internet Explorer 10 hat Microsoft sich angepasst und übergibt die weiteren Parameter an das Funktionsobjekt. Wenn das Programm auch mit älteren Browserfunktionen laufen soll, sollte man auf weitere Parameter verzichten und statt dessen die nachfolgend gezeigte Variante mit einer anonymen Funktion verwenden, die die benötigten Zusatzdaten als Closure einschließt.
Parameterübergabe an die Timeout-Funktion ansehen …
function timeOut(val1, val2) {
  document.getElementById('id1').innerHTML = val1;
  document.getElementById('id2').innerHTML = val2;
}
setTimeout(timeOut, 2000, 'a', 'b');
<p>Wert 1: <span id="id1"></span></p>
<p>Wert 2: <span id="id2"></span></p>

Verwendung einer anonymen Funktion

Man kann auch eine anonyme Funktion als ersten Parameter übergeben, in der dann der auszuführende Code steht.

Beispiel
<html>
  <head>
    <title>Timeout-test</title>
    <script type="text/javascript">
    setTimeout(
      function(val1, val2) {
        document.getElementById('id1').innerHTML = val1;
        document.getElementById('id2').innerHTML = val2;
      },
      2000,
      'a', 'b');
    </script>
  </head>
  <body>
    <p>Wert 1: <span id="id1"></span></p>
    <p>Wert 2: <span id="id2"></span></p>
  </body>
</html>
Beachten Sie: In Google Chrome, Firefox und Opera wird auf inaktiven Tabs maximal ein Timeout pro Sekunde ausgelöst, um Rechenleistung zu sparen.[1] Timeouts mit geringen Verzögerungen werden also erst später als erwartet ausgelöst. Um Animationen trotzdem zeitlich korrekt ausführen zu können, wird empfohlen, requestAnimationFrame zu benutzen. Diese Funktion hat einige weitere Vorteile gegenüber setTimeout.

Probleme mit Multithreading und Selbstunterbrechung

Falls Sie nicht wissen, was das ist: Parallelausführung, oder Multithreading, bedeutet, dass ein Programm mehrere Codeteile gleichzeitig ausführt. Auf modernen Mehrkern-Prozessoren kann man damit die Verarbeitung enorm beschleunigen. Es bedeutet aber auch, dass Variablen von zwei gleichzeitig laufenden Codestücken genutzt werden können, und dass man Programmcode braucht, um diese Nutzung zu synchronisieren.

Selbstunterbrechung ist etwas, das bei Timern mit hoher Frequenz vorkommen kann. Während noch auf ein Timer-Ereignis reagiert wird, schlägt bereits das nächste zu. Der Computer tut nichts anderes mehr, als auf den Timer zu reagieren.

In vielen Programmierumgebungen sind Synchronisierung und Selbstunterbrechung knifflige Probleme. Die gute Nachricht lautet: In JavaScript nicht. Die schlechte Nachricht hinter der guten Nachricht ist allerdings: es gibt keine Nebenläufigkeit, die zu synchronisieren wäre, und auch keine Echtzeitreaktion auf Ereignisse.

Ein Timer löst keine Code-Unterbrechung aus. Die vom Timer aufgerufene Funktion läuft auch nicht parallel zu übrigem Code. Es ist in JavaScript nicht vorgesehen, in einem Ausführungskontext mehr als einen Thread laufen zu lassen. Für echte Parallelverarbeitung benötigen Sie Web-Worker, deren Daten aber strikt getrennt sind.

Wenn ein Timer abläuft, wird die hinterlegte Funktion **nicht** unverzüglich aufgerufen. Statt dessen wird ein Eintrag in der so genannten Makrotask-Queue erzeugt. Diese Queue ist zentrale Drehscheibe für JavaScript, jedes Event, egal woher, führt zu einem neuen Eintrag in dieser Queue, und die in dieser Queue hinterlegten Einträge werden sequenziell abgearbeitet.

Das bedeutet: wenn Sie eine lange laufende JavaScript-Funktion schreiben, blockieren Sie diese Queue. Wenn Sie zu Beginn einer lange laufenden JavaScript-Funktion setTimeout aufrufen, um "ein paar Dinge im Hintergrund zu erledigen", passiert das nicht. Insbesondere ist es auch bei sehr kurzen Timeout-Intervalle nicht möglich, dass sich der Timeout-Handler "selbst unterbricht".

Zeitsprünge vermeiden

Empfehlung: Bei umfangreichen Funktionen sollte setTimeout so früh wie möglich eingesetzt werden, damit die Berechnungszeit der weiteren Zuweisungen und Operationen nicht die Sekundenzählung (gerade am Anfang und am Schluss) springen lässt.

Bei dieser Empfehlung geht nicht um Multithreading-Probleme, sondern darum, dass die Zeit bis zum Aufruf der Timeout-Funktion ab dem Moment gemessen wird, wo Sie setTimeout aufrufen. Wenn eine Webseite bestimmte Dinge in einem Zeittakt tun soll (z. B. eine Uhrzeitanzeige aktualisieren), dann macht man das oft so, dass die dafür zuständige Funktion ihren nächsten Aufruf mit setTimeout selbst beauftragt.

Wenn aber eine Timeout-Funktion namens showTime beispielsweise 100 Millisekunden lang läuft und erst am Ende showTime(func, 1000) aufruft, um nach einer Sekunde ihren nächsten Aufruf zu veranlassen, dann addieren sich Laufzeit und Wartezeit zu 1,1 Sekunden.

Wenn man sich von der Laufzeit der Timeout-Funktion unabhängig machen möchte, ist setTimeout nicht das beste Mittel, statt dessen bieten sich setInterval und requestAnimationFrame an.

Anwendungsbeispiel

Countdown

Beispiel ansehen …
let elem = document.getElementById('los');
elem.addEventListener('click', startCountdown);	
  
let elem = document.getElementById('stop');
elem.addEventListener('click', stopCountdown);	

function startCountdown () {
  countdown(10, 'counter');
  EnableCancelButton (true);
}

function stopCountdown() {
  var element = document.getElementById('counter');
  clearTimeout(element.timerId);
  EnableCancelButton(false);
}

function EnableCancelButton(enable) {
  let cancelButton = document.getElementById ("stop");
  cancelButton.disabled = !enable;
}
        
function leadingzero(number) {
  return (number < 10) ? '0' + number : number;
}

function countdown(seconds, target) {
  let element = document.getElementById(target);
  element.seconds = seconds;
  calculateAndShow(target);
}

function calculateAndShow(target) {
  let element = document.getElementById(target);
  if (element.seconds >= 0) {
    element.timerId = window.setTimeout(calculateAndShow,1000,target);
    let h = Math.floor(element.seconds / 3600);
    let m = Math.floor((element.seconds % 3600) / 60);
    let s = element.seconds % 60;
    element.innerHTML =
    leadingzero(h) + ':' +
    leadingzero(m) + ':' +
    leadingzero(s);
    element.seconds--;
  } else {
    completed(target);
    return false;
  }
}

function completed(target) {
  let element = document.getElementById(target);
  element.innerHTML = "<strong>Liftoff!<\/strong>";
}

Es wird mit setTimeout() ein Countdown gestartet, der mit einem Klick auf den Stop-Button mittels clearTimeout() unterbrochen wird.

Aufwändige Berechnungen nicht zu oft ausführen

In diesem Beispiel soll gezeigt werden, wie man eine automatische Neuberechnung von angezeigten Werten durchführt und dabei darauf achtet, dass das nicht zu häufig geschieht. Im Beispiel werden lediglich einige Zahlen addiert, aber stellen Sie sich vor, die Berechnung müsste auf einem Server erfolgen. Eine Neuberechnung nach jedem Tastendruck würde den Server mit Anfragen überfluten.

Das Script reagiert auf jede Veränderung in den Eingabefeldern (input-Event) und startet die Neuberechnung mittels setTimeout. Zuvor wird aber eine eventuell vorher schon ausgelöste Neuberechnung gelöscht. Der Effekt ist, dass man die Werte fleißig ändern kann, aber erst dann wirklich neu berechnet wird, wenn man dem System eine halbe Sekunden Ruhe lässt.

Beispiel ansehen …
<h1>Addition</h1>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Wert: <input type="number"></label>
<label>Summe: <output id="summe"></output></label>

<script>
   let timeoutId;
   
   document.body.addEventListener("input", function() {
      clearTimeout(timeoutId);
      timeoutId = setTimeout(recalculate, 500);
   });
   
   function recalculate() {
      let sum = 0;
      for (let numEntry of document.querySelectorAll("input[type=number]")) {
         if (!isNaN(numEntry.valueAsNumber))
            sum += numEntry.valueAsNumber;
      }
      document.getElementById("summe").textContent = sum;
   }
</script>

Siehe auch:

Weblinks

  1. MDN: setTimeout inactive tabs