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.