Hallo Kollegen. Es war einmal
ein Artikel über Habré von John Rezig zu diesem Thema. 10 Jahre sind vergangen, und das Thema muss noch geklärt werden. Daher bieten wir Interessenten an, den Artikel von Samer Buna zu lesen, der nicht nur einen theoretischen Überblick über Timer in JavaScript (im Kontext von Node.js) gibt, sondern auch Aufgaben zu diesen.

Vor einigen Wochen habe ich die folgende Frage aus einem einzigen Interview getwittert:
„Wo ist der Quellcode für die Funktionen setTimeout und setInterval? Wo würdest du ihn suchen? Du kannst es nicht googeln :) "
*** Beantworte es selbst und lies dann weiter ***
Etwa die Hälfte der Antworten auf diesen Tweet war falsch. Nein, der Fall ist NICHT mit V8 (oder anderen VMs) verbunden !!! Funktionen wie
setTimeout
und
setInterval
, die stolz als JavaScript-JavaScript-Timer bezeichnet werden, sind nicht Teil einer ECMAScript-Spezifikation oder einer JavaScript-Engine-Implementierung. Timer-Funktionen werden auf Browserebene implementiert, sodass sich ihre Implementierung in verschiedenen Browsern unterscheidet. Timer sind auch nativ in der Node.js.-Laufzeit selbst implementiert.
In Browsern beziehen sich die Hauptzeitgeberfunktionen auf die
Window
, die auch einigen anderen Funktionen und Objekten zugeordnet ist. Diese Schnittstelle bietet globalen Zugriff auf alle Elemente im Hauptbereich von JavaScript. Aus diesem Grund kann die Funktion
setTimeout
direkt in der Browserkonsole ausgeführt werden.
In Node sind Timer Teil des
global
Objekts, das wie die Browser-Oberfläche von
Window
. Der Quellcode für die Timer in Node wird
hier angezeigt.
Es mag jemandem scheinen, dass dies nur eine schlechte Frage aus dem Interview ist - was nützt es, das zu wissen ?! Ich als JavaScript-Entwickler denke so: Es wird davon ausgegangen, dass Sie dies wissen sollten, da das Gegenteil darauf hindeutet, dass Sie nicht ganz verstehen, wie V8 (und andere virtuelle Maschinen) mit Browsern und Node interagieren.
Schauen wir uns ein paar Beispiele an und lösen ein paar Timer-Aufgaben.
Mit dem Befehl node können Sie die Beispiele in diesem Artikel ausführen. Die meisten der hier diskutierten Beispiele wurden in meinem Kurs Erste Schritte mit Node.js zu Pluralsight vorgestellt.Verzögerung der FunktionsausführungTimer sind Funktionen höherer Ordnung, mit denen Sie die Ausführung anderer Funktionen verzögern oder wiederholen können (der Timer erhält eine solche Funktion als erstes Argument).
Hier ist ein Beispiel für eine verzögerte Ausführung:
In diesem Beispiel wird die Begrüßungsnachricht bei Verwendung von
setTimeout
um 4 Sekunden verzögert. Das zweite Argument für
setTimeout
ist die Verzögerung (in ms). Ich multipliziere 4 mit 1000, um 4 Sekunden zu erhalten.
Das erste Argument für
setTimeout
ist eine Funktion, deren Ausführung verzögert wird.
Wenn Sie die Datei
example1.js
mit dem Befehl node ausführen, hält Node 4 Sekunden lang an und zeigt dann eine Willkommensnachricht an (gefolgt von einem Exit).
Bitte beachten Sie: Das erste Argument für
setTimeout
ist nur eine
Funktionsreferenz . Es sollte keine eingebaute Funktion sein - wie zum Beispiel
example1.js
. Hier ist das gleiche Beispiel ohne Verwendung der integrierten Funktion:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
Argumente übergebenWenn die Funktion, für die
setTimeout
zum Verzögern verwendet wird, Argumente akzeptiert, können Sie die verbleibenden Argumente der Funktion
setTimeout
selbst (nach den 2, die wir bereits untersucht haben) verwenden, um die Werte der Argumente an die verzögerte Funktion zu übertragen.
Hier ist ein Beispiel:
Die obige
rocks
Funktion, die um 2 Sekunden verzögert ist, verwendet das
who
Argument, und beim Aufrufen von
setTimeout
wird der Wert "Node.js" als solches
who
Argument übergeben.
Wenn Sie
example2.js
mit dem Befehl
node
ausführen, wird nach 2 Sekunden der Ausdruck "Node.js rockt" angezeigt.
Timer Aufgabe 1Basierend auf dem bereits untersuchten Material zu
setTimeout
werden die folgenden 2 Meldungen nach den entsprechenden Verzögerungen angezeigt.
- Die Meldung „Hallo nach 4 Sekunden“ wird nach 4 Sekunden angezeigt.
- Die Meldung „Hallo nach 8 Sekunden“ wird nach 8 Sekunden angezeigt.
EinschränkungIn Ihrer Lösung können Sie nur eine Funktion definieren, die integrierte Funktionen enthält. Dies bedeutet, dass viele
setTimeout
Aufrufe dieselbe Funktion verwenden müssen.
LösungSo würde ich dieses Problem lösen:
Für mich empfängt
theOneFunc
das
delay
und verwendet den Wert dieses
delay
in der auf dem Bildschirm angezeigten Nachricht. Somit kann die Funktion unterschiedliche Meldungen anzeigen, je nachdem, welchen Verzögerungswert wir darüber informieren.
Dann habe ich
theOneFunc
in zwei
setTimeout
Aufrufen verwendet, wobei der erste Aufruf nach 4 Sekunden und der zweite nach 8 Sekunden ausgelöst wurde. Beide
setTimeout
Aufrufe erhalten auch ein
setTimeout
Argument, das das
delay
für
theOneFunc
.
Wenn Sie die Datei
solution1.js
mit dem Befehl node ausführen, werden die Anforderungen der Aufgabe angezeigt. Die erste Meldung wird nach 4 Sekunden und die zweite nach 8 Sekunden angezeigt.
Wiederholen Sie die FunktionAber was ist, wenn ich Sie gebeten habe, unbegrenzt alle 4 Sekunden eine Nachricht anzuzeigen?
Natürlich können Sie
setTimeout
in eine Schleife
setTimeout
, aber die Timer-API bietet auch die Funktion
setInterval
, mit der Sie die „ewige“ Ausführung jeder Operation programmieren können.
Hier ist ein Beispiel für
setInterval
:
Dieser Code zeigt alle 3 Sekunden eine Meldung an. Wenn Sie
example3.js
mit dem Befehl
node
ausführen, gibt Node diesen Befehl aus, bis Sie das
example3.js
des Prozesses erzwingen (STRG + C).
Timer abbrechenDa beim Aufrufen der Timer-Funktion eine Aktion zugewiesen wird, kann diese Aktion auch rückgängig gemacht werden, bevor sie ausgeführt wird.
Der Aufruf von
setTimeout
gibt eine Timer-ID zurück. Sie können diese Timer-ID beim Aufrufen von
clearTimeout
, um den Timer abzubrechen. Hier ist ein Beispiel:
Dieser einfache Timer sollte nach 0 ms (
timerId
sofort)
clearTimeout
. Dies ist jedoch nicht der Fall, da wir den Wert von
timerId
und diesen Timer sofort durch Aufrufen von
clearTimeout
.
Wenn Sie
example4.js
mit dem Befehl
node
ausführen,
example4.js
Node nichts - der Vorgang wird einfach sofort beendet.
Übrigens bietet Node.js auch eine andere Möglichkeit,
setTimeout
mit einem Wert von 0 ms
setTimeout
. In der Node.js-Timer-API gibt es eine weitere Funktion namens
setImmediate
, die im Grunde dasselbe wie
setTimeout
mit einem Wert von 0 ms
setTimeout
In diesem Fall können Sie jedoch die Verzögerung weglassen:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
Die Funktion
setImmediate
wird
setImmediate
in allen Browsern unterstützt . Verwenden Sie es nicht im Client-Code.
Zusammen mit
clearTimeout
gibt es eine
clearInterval
Funktion, die dasselbe tut, jedoch mit
setInerval
Aufrufen, und es gibt auch einen
clearImmediate
Aufruf.
Timer Delay - eine Sache, die nicht garantiert istHaben Sie bemerkt, dass im vorherigen Beispiel beim Ausführen einer Operation mit
setTimeout
nach 0 ms diese Operation nicht sofort (nach
setTimeout
) ausgeführt wird, sondern erst, nachdem der gesamte
clearTimeout
vollständig ausgeführt wurde (einschließlich des Aufrufs
clearTimeout
)?
Lassen Sie mich diesen Punkt anhand eines Beispiels verdeutlichen. Hier ist ein einfacher
setTimeout
Aufruf, der in einer halben Sekunde funktionieren sollte - aber das passiert nicht:
Unmittelbar nach dem Definieren des Timers in diesem Beispiel blockieren wir die Laufzeitumgebung synchron mit einer großen
for
Schleife. Der Wert von
1e10
ist 1 mit 10 Nullen, sodass der Zyklus 10 Milliarden Prozessorzyklen dauert (im Prinzip simuliert dies einen überlasteten Prozessor). Der Knoten kann nichts tun, bis diese Schleife abgeschlossen ist.
In der Praxis ist dies natürlich sehr schlecht, aber dieses Beispiel hilft zu verstehen, dass die
setTimeout
Verzögerung nicht garantiert ist, sondern der
Mindestwert . Ein Wert von 500 ms bedeutet, dass die Verzögerung mindestens 500 ms beträgt. Tatsächlich dauert es viel länger, bis das Skript die Begrüßungszeile auf dem Bildschirm anzeigt. Zunächst muss er warten, bis der Sperrzyklus abgeschlossen ist.
Timer Problem # 2Schreiben Sie ein Skript, in dem die Meldung „Hallo Welt“ einmal pro Sekunde, jedoch nur fünfmal angezeigt wird. Nach 5 Iterationen sollte das Skript die Meldung "Fertig" anzeigen. Danach wird der Knotenprozess abgeschlossen.
Einschränkung : Wenn Sie dieses Problem lösen, können Sie
setTimeout
nicht aufrufen.
Hinweis : brauche einen Zähler.
LösungSo würde ich dieses Problem lösen:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
Ich setze 0 als Anfangswert des
counter
und
setInterval
dann
setInterval
, das seine ID annimmt.
Eine verzögerte Funktion zeigt eine Meldung an und erhöht den Zähler jedes Mal um eins. Innerhalb der verzögerten Funktion haben wir eine if-Anweisung, die prüft, ob bereits 5 Iterationen bestanden wurden. Nach 5 Iterationen zeigt das Programm „Fertig“ an und löscht den Intervallwert mithilfe der erfassten
intervalId
Konstante. Die Intervallverzögerung beträgt 1000 ms.
Wer nennt genau verzögerte Funktionen?Bei Verwendung von JavaScript
this
in einer regulären Funktion, wie zum Beispiel:
function whoCalledMe() { console.log('Caller is', this); }
Der Wert im
this
mit dem
Anrufer überein. Wenn Sie die obige Funktion innerhalb des Knotens REPL definieren, wird sie vom
global
Objekt aufgerufen. Wenn Sie eine Funktion in der Browserkonsole definieren, wird sie vom
window
aufgerufen.
Definieren wir eine Funktion als Eigenschaft eines Objekts, um es etwas klarer zu machen:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };
Wenn wir nun bei der Arbeit mit der Funktion
obj
den Link direkt dazu verwenden, fungiert das Objekt
obj
(identifiziert durch seine
id
) als Aufrufer:

Die Frage ist nun: Wer wird der Anrufer sein, wenn Sie den Link an
obj.whoCallMe
an
setTimetout
?
Wer ist in diesem Fall der Anrufer?Die Antwort hängt davon ab, wo die Timer-Funktion ausgeführt wird. In diesem Fall ist die Abhängigkeit davon, wer der Anrufer ist, einfach nicht akzeptabel. Sie verlieren die Kontrolle über den Aufrufer, da dies von der Implementierung des Timers abhängt, der in diesem Fall Ihre Funktion aufruft. Wenn Sie diesen Code in einem Node REPL testen, ist das
Timeout
Objekt der Aufrufer:

Bitte beachten Sie: Dies ist nur wichtig, wenn das JavaScript
this
in regulären Funktionen verwendet wird. Bei Verwendung der Pfeilfunktionen sollte der Anrufer Sie überhaupt nicht stören.
Timer Problem # 3Schreiben Sie ein Skript, das kontinuierlich eine Nachricht "Hallo Welt" mit unterschiedlichen Verzögerungen ausgibt. Beginnen Sie mit einer Verzögerung von einer Sekunde und erhöhen Sie diese bei jeder Iteration um eine Sekunde. Bei der zweiten Iteration beträgt die Verzögerung 2 Sekunden. Am dritten - drei und so weiter.
Fügen Sie der angezeigten Nachricht eine Verzögerung hinzu. Sie sollten so etwas bekommen:
Hello World. 1
Hello World. 2
Hello World. 3
...
Einschränkungen : Variablen können nur mit const definiert werden. Let oder var verwenden ist nicht.
LösungDa die Dauer der Verzögerung in dieser Aufgabe eine Variable ist, können Sie
setInterval
hier nicht verwenden, aber Sie können die Intervallausführung mithilfe von
setTimeout
innerhalb eines rekursiven Aufrufs manuell konfigurieren. Die erste mit
setTimeout
ausgeführte Funktion erstellt den nächsten Timer usw.
Da Sie
let
/
var
nicht verwenden können, können wir außerdem keinen Zähler haben, um die Verzögerung für jeden rekursiven Aufruf zu erhöhen. Stattdessen können Sie die Argumente einer rekursiven Funktion verwenden, um während eines rekursiven Aufrufs ein Inkrement durchzuführen.
So lösen Sie dieses Problem:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
Timer Aufgabe 4Schreiben Sie ein Skript, das die Nachricht „Hello World“ mit der gleichen Verzögerungsstruktur wie in Aufgabe 3 anzeigt, diesmal jedoch in Gruppen von 5 Nachrichten, und die Gruppe hat ein Hauptverzögerungsintervall. Für die erste Gruppe von 5 Nachrichten wählen wir die anfängliche Verzögerung von 100 ms, für die nächsten - 200 ms, für die dritten - 300 ms und so weiter.
So sollte dieses Skript funktionieren:
- Bei 100 ms zeigt das Skript zum ersten Mal „Hallo Welt“ an, und zwar fünfmal mit einem Intervall von 100 ms. Die erste Meldung erscheint nach 100 ms, die zweite nach 200 ms usw.
- Nach den ersten 5 Nachrichten sollte das Skript die Hauptverzögerung um 200 ms erhöhen. Somit wird die 6. Nachricht nach 500 ms + 200 ms (700 ms), die 7. - 900 ms, die 8. Nachricht - nach 1100 ms usw. angezeigt.
- Nach 10 Nachrichten sollte das Skript das Hauptverzögerungsintervall um 300 ms erhöhen. Die 11. Meldung sollte nach 500 ms + 1000 ms + 300 ms (18000 ms) angezeigt werden. Die 12. Meldung sollte nach 2100 ms usw. angezeigt werden.
Nach diesem Prinzip sollte das Programm auf unbestimmte Zeit funktionieren.
Fügen Sie der angezeigten Nachricht eine Verzögerung hinzu. Sie sollten so etwas bekommen (kein Kommentar):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
Einschränkungen : Sie können nur Aufrufe von
setInterval
(und nicht
setTimeout
) und nur ONE
if
.
LösungDa wir nur mit
setInterval
Aufrufen arbeiten können, müssen wir hier die Rekursion verwenden und auch die Verzögerung des nächsten
setInterval
Aufrufs
setInterval
. Außerdem benötigen wir die
if
, um dies erst nach 5 Aufrufen dieser rekursiven Funktion zu erreichen.
Hier ist eine mögliche Lösung:
let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100);
Vielen Dank an alle, die es gelesen haben.