Il y a quelques semaines, j'ai tweeté cette question d'entretien:
*** Répondez à la question dans votre tête maintenant avant de continuer ***
Environ la moitié des réponses au Tweet étaient fausses. La réponse n'est PAS V8 (ou d'autres VM) !! Bien que connues sous le nom de «JavaScript Timers», les fonctions aiment setTimeout
et setInterval
ne font pas partie des spécifications ECMAScript ou des implémentations de moteur JavaScript. Les fonctions de minuterie sont implémentées par les navigateurs et leurs implémentations seront différentes selon les navigateurs. Les minuteries sont également implémentées nativement par le runtime Node.js lui-même.
Dans les navigateurs, les principales fonctions du minuteur font partie de l' Window
interface, qui comporte quelques autres fonctions et objets. Cette interface rend tous ses éléments disponibles globalement dans la portée JavaScript principale. C'est pourquoi vous pouvez exécuter setTimeout
directement dans la console de votre navigateur.
Dans Node, les minuteries font partie de l' global
objet, qui se comporte de la même manière que l' Window
interface du navigateur . Vous pouvez voir le code source des minuteries dans Node ici.
Certains pourraient penser que c'est une mauvaise question d'entrevue - pourquoi le savoir de toute façon est-il important?! En tant que développeur JavaScript, je pense que vous êtes censé le savoir car si vous ne le faites pas, cela pourrait être le signe que vous ne comprenez pas complètement comment V8 (et d'autres VM) interagit avec les navigateurs et Node.
Faisons quelques exemples et défis concernant les fonctions de minuterie, d'accord?
Mise à jour: Cet article fait maintenant partie de mon «Introduction complète à Node.js».Vous pouvez en lire la version mise à jour ici.
Retarder l'exécution d'une fonction
Les fonctions de minuterie sont des fonctions d'ordre supérieur qui peuvent être utilisées pour retarder ou répéter l'exécution d'autres fonctions (qu'elles reçoivent comme premier argument).
Voici un exemple de retard:
// example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 );
Cet exemple utilise setTimeout
pour retarder l'impression du message d'accueil de 4 secondes. Le deuxième argument de setTimeout
est le délai (en ms). C'est pourquoi j'ai multiplié 4 par 1000 pour en faire 4 secondes.
Le premier argument de setTimeout
est la fonction dont l'exécution sera retardée.
Si vous exécutez le example1.js
fichier avec la node
commande, Node se mettra en pause pendant 4 secondes, puis il imprimera le message d'accueil (et quittera après cela).
Notez que le premier argument de setTimeout
n'est qu'une référence de fonction . Il n'est pas nécessaire que ce soit une fonction en ligne comme ce qui example1.js
a été le cas. Voici le même exemple sans utiliser de fonction en ligne:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
Passer des arguments
Si la fonction qui utilise setTimeout
pour retarder son exécution accepte des arguments, nous pouvons utiliser les arguments restants pour setTimeout
elle-même (après les 2 que nous avons appris jusqu'à présent) pour relayer les valeurs d'argument à la fonction retardée.
// For: func(arg1, arg2, arg3, ...) // We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)
Voici un exemple:
// example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js');
La rocks
fonction ci-dessus, qui est retardée de 2 secondes, accepte un who
argument et l' setTimeout
appel relaie la valeur « Node.js » comme cet who
argument.
L'exécution example2.js
avec la node
commande affichera « Node.js rocks » après 2 secondes.
Défi Timers # 1
En utilisant ce que vous avez appris jusqu'à présent setTimeout
, imprimez les 2 messages suivants après leurs délais correspondants.
- Imprimer le message « Bonjour après 4 secondes » après 4 secondes
- Imprimez le message « Bonjour après 8 secondes » après 8 secondes.
Contraintes :
Vous ne pouvez définir qu'une seule fonction dans votre solution, qui inclut des fonctions en ligne. Cela signifie que de nombreux setTimeout
appels devront utiliser exactement la même fonction.
Solution
Voici comment je résoudrais ce défi:
// solution1.js const theOneFunc = delay => { console.log('Hello after ' + delay + ' seconds'); }; setTimeout(theOneFunc, 4 * 1000, 4); setTimeout(theOneFunc, 8 * 1000, 8);
J'ai fait theOneFunc
recevoir un delay
argument et utilisé la valeur de cet delay
argument dans le message imprimé. De cette façon, la fonction peut imprimer différents messages en fonction de la valeur de retard que nous lui transmettons.
J'ai ensuite utilisé theOneFunc
dans deux setTimeout
appels, un qui se déclenche après 4 secondes et un autre qui se déclenche après 8 secondes. Ces deux setTimeout
appels reçoivent également un troisième argument pour représenter l' delay
argument pour theOneFunc
.
L'exécution du solution1.js
fichier avec la node
commande imprimera les exigences du défi, le premier message après 4 secondes et le deuxième message après 8 secondes.
Répéter l'exécution d'une fonction
Et si je vous demandais d'imprimer un message toutes les 4 secondes, pour toujours?
Bien que vous puissiez mettre setTimeout
en boucle, l'API des minuteries offre également la setInterval
fonction, ce qui accomplirait l'exigence de faire quelque chose pour toujours.
Voici un exemple de setInterval:
// example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 );
This example will print its message every 3 seconds. Executing example3.js
with the node
command will make Node print this message forever, until you kill the process (with CTRL+C).
Cancelling Timers
Because calling a timer function schedules an action, that action can also be cancelled before it gets executed.
A call to setTimeout
returns a timer “ID” and you can use that timer ID with a clearTimeout
call to cancel that timer. Here’s an example:
// example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId);
This simple timer is supposed to fire after 0
ms (making it immediate), but it will not because we are capturing the timerId
value and canceling it right after with a clearTimeout
call.
When we execute example4.js
with the node
command, Node will not print anything and the process will just exit.
By the way, in Node.js, there is another way to do setTimeout
with 0
ms. The Node.js timer API has another function called setImmediate
, and it’s basically the same thing as a setTimeout
with a 0
ms but we don’t have to specify a delay there:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
The setImmediate
function is not available in all browsers. Don’t use it for front-end code.
Just like clearTimeout
, there is also a clearInterval
function, which does the same thing but for setInerval
calls, and there is also a clearImmediate
call as well.
A timer delay is not a guaranteed thing
In the previous example, did you notice how executing something with setTimeout
after 0
ms did not mean execute it right away (after the setTimeout line), but rather execute it right away after everything else in the script (including the clearTimeout call)?
Let me make this point clear with an example. Here’s a simple setTimeout
call that should fire after half a second, but it won’t:
// example5.js setTimeout( () => console.log('Hello after 0.5 seconds. MAYBE!'), 500, ); for (let i = 0; i < 1e10; i++) { // Block Things Synchronously }
Right after defining the timer in this example, we block the runtime synchronously with a big for
loop. The 1e10
is 1
with 10
zeros in front of it, so the loop is a 10
Billion ticks loop (which basically simulates a busy CPU). Node can do nothing while this loop is ticking.
This of course is a very bad thing to do in practice, but it’ll help you here to understand that setTimeout
delay is not a guaranteed thing, but rather a minimum thing. The 500
ms means a minimum delay of 500
ms. In reality, the script will take a lot longer to print its greeting line. It will have to wait on the blocking loop to finish first.
Timers Challenge #2
Write a script to print the message “Hello World” every second, but only 5 times. After 5 times, the script should print the message “Done” and let the Node process exit.
Constraints: You cannot use a setTimeout
call for this challenge.
Hint: You need a counter.
Solution
Here’s how I’d solve this one:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
I initiated a counter
value as 0
and then started a setInterval
call capturing its id.
The delayed function will print the message and increment the counter each time. Inside the delayed function, an if
statement will check if we’re at 5
times by now. If so, it’ll print “Done” and clear the interval using the captured intervalId
constant. The interval delay is 1000
ms.
Who exactly “calls” the delayed functions?
When you use the JavaScript this
keyword inside a regular function, like this:
function whoCalledMe() { console.log('Caller is', this); }
The value inside the this
keyword will represent the caller of the function. If you define the function above inside a Node REPL, the caller will be the global
object. If you define the function inside a browser’s console, the caller will be the window
object.
Let’s define the function as a property on an object to make this a bit more clear:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } }; // The function reference is now: obj.whoCallMe
Now when you call the obj.whoCallMe
function using its reference directly, the caller will be the obj
object (identified by its id):

Now, the question is, what would the caller be if we pass the reference of obj.whoCallMe
to a setTimetout
call?
// What will this print?? setTimeout(obj.whoCalledMe, 0);
Who will the caller be in that case?
The answer is different based on where the timer function is executed. You simply can’t depend on who the caller is in that case. You lose control of the caller because the timer implementation will be the one invoking your function now. If you test it in a Node REPL, you’d get a Timetout
object as the caller:

Note that this only matters if you’re using JavaScript’s this
keyword inside regular functions. You don’t need to worry about the caller at all if you’re using arrow functions.
Timers Challenge #3
Write a script to continuously print the message “Hello World” with varying delays. Start with a delay of 1 second and then increment the delay by 1 second each time. The second time will have a delay of 2 seconds. The third time will have a delay of 3 seconds, and so on.
Include the delay in the printed message. Expected output looks like:
Hello World. 1 Hello World. 2 Hello World. 3 ...
Constraints: You can only use const
to define variables. You can’t use let
or var
.
Solution
Because the delay amount is a variable in this challenge, we can’t use setInterval
here, but we can manually create an interval execution using setTimeout
within a recursive call. The first executed function with setTimeout will create another timer, and so on.
Also, because we can’t use let/var, we can’t have a counter to increment the delay in each recursive call, but we can instead use the recursive function arguments to increment during the recursive call.
Here’s one possible way to solve this challenge:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
Timers Challenge #4
Write a script to continuously print the message “Hello World” with the same varying delays concept as challenge #3, but this time, in groups of 5 messages per main-delay interval. Starting with a delay of 100ms for the first 5 messages, then a delay of 200ms for the next 5 messages, then 300ms, and so on.
Here’s how the script should behave:
- At the 100ms point, the script will start printing “Hello World” and do that 5 times with an interval of 100ms. The 1st message will appear at 100ms, 2nd message at 200ms, and so on.
- After the first 5 messages, the script should increment the main delay to 200ms. So 6th message will be printed at 500ms + 200ms (700ms), 7th message will be printed at 900ms, 8th message will be printed at 1100ms, and so on.
- After 10 messages, the script should increment the main delay to 300ms. So the 11th message should be printed at 500ms + 1000ms + 300ms (18000ms). The 12th message should be printed at 21000ms, and so on.
- Continue the pattern forever.
Include the delay in the printed message. The expected output looks like this (without the comments):
Hello World. 100 // At 100ms Hello World. 100 // At 200ms Hello World. 100 // At 300ms Hello World. 100 // At 400ms Hello World. 100 // At 500ms Hello World. 200 // At 700ms Hello World. 200 // At 900ms Hello World. 200 // At 1100ms ...
Constraints: You can use only setInterval
calls (not setTimeout
) and you can use only ONE if statement.
Solution
Because we can only use setInterval
calls, we’ll need recursion here as well to increment the delay of the next setInterval
call. In addition, we need an if statement to control doing that only after 5 calls of that recursive function.
Here’s one possible solution:
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);
Thanks for reading.
If you’re just beginning to learn Node.js, I recently published a first-steps course at Pluralsight, check it out:
