„JavaScript“ laikmačiai: viskas, ką reikia žinoti

Prieš kelias savaites tweetavau šį interviu klausimą:

*** Dabar atsakykite į galvą, prieš tęsdami ***

Maždaug pusė „Twitter“ atsakymų buvo neteisingi. Atsakymas NĖRA V8 (ar kitų VM) !! Nors žinomos kaip „JavaScript“ laikmačiai “, jos panašios į ECMAScript specifikacijas ar bet kurias„ JavaScript “variklio versijas setTimeoutir setIntervalnėra jų dalys. Laikmačio funkcijas įgyvendina naršyklės, o skirtingose ​​naršyklėse jos bus skirtingos. Laikmačius taip pat natūraliai įgyvendina pats „Node.js“ vykdymo laikas.

Naršyklėse pagrindinės laikmačio funkcijos yra Windowsąsajos dalis, turinti keletą kitų funkcijų ir objektų. Ta sąsaja padaro visus jos elementus prieinamus visame pasaulyje pagrindinėje „JavaScript“ srityje. Štai kodėl galite vykdyti setTimeouttiesiogiai naršyklės konsolėje.

„Node“ laikmačiai yra globalobjekto dalis, kuri elgiasi panašiai kaip naršyklės Windowsąsaja. Laikmačių šaltinio kodą „Node“ galite pamatyti čia.

Kai kas gali pagalvoti, kad tai blogas interviu klausimas - kodėl vis tiek žinant šį reikalą ?! Kaip „JavaScript“ kūrėjas, manau, tikimasi, kad tai žinosite, nes jei to nepadarysite, tai gali būti ženklas, kad jūs ne visiškai suprantate, kaip V8 (ir kitos VM) sąveikauja su naršyklėmis ir „Node“.

Padarykime keletą pavyzdžių ir iššūkių, susijusių su laikmačio funkcijomis, ar ne?

Atnaujinimas: Šis straipsnis dabar yra mano „Pilno įvado į Node.js“ dalis.

Atnaujintą jos versiją galite perskaityti čia.

Funkcijos vykdymo atidėjimas

Laikmačio funkcijos yra aukštesnės eilės funkcijos, kuriomis galima atidėti ar pakartoti kitų funkcijų vykdymą (kurias jos gauna kaip pirmąjį argumentą).

Štai pavyzdys apie vėlavimą:

// example1.js setTimeout( () => { console.log('Hello after 4 seconds'); }, 4 * 1000 );

Šiame pavyzdyje setTimeoutsveikinimo pranešimo spausdinimas atidedamas 4 sekundėmis. Antrasis argumentas setTimeoutyra vėlavimas (ms). Štai kodėl aš padauginau 4 iš 1000, kad pavyktų į 4 sekundes.

Pirmasis argumentas setTimeoutyra funkcija, kurios vykdymas bus atidėtas.

Jei vykdote example1.jsfailą naudodami nodekomandą, mazgas pristabdys 4 sekundes ir išspausdins sveikinimo pranešimą (o po to išeis).

Atminkite, kad pirmasis argumentas setTimeoutyra tik funkcijos nuoroda . Tai nebūtinai turi būti tiesioginė funkcija, kokia example1.jsyra. Čia tas pats pavyzdys nenaudojant įterptinės funkcijos:

const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);

Argumentų perdavimas

Jei funkcija, setTimeoutatidedanti jos vykdymą, priima bet kokius argumentus, likusius argumentus galime naudoti setTimeoutsau (po 2, apie kuriuos sužinojome iki šiol), kad argumentų reikšmes perduotume uždelstai funkcijai.

// For: func(arg1, arg2, arg3, ...) // We can use: setTimeout(func, delay, arg1, arg2, arg3, ...)

Štai pavyzdys:

// example2.js const rocks = who => { console.log(who + ' rocks'); }; setTimeout(rocks, 2 * 1000, 'Node.js');

Aukščiau nurodyta rocksfunkcija, atidėta 2 sekundėmis, priima whoargumentą, o setTimeoutiškvietimas perduoda reikšmę „ Node.js “ kaip šį whoargumentą.

Vykdant example2.jssu nodekomanda bus išspausdinti " Node.js akmenis " po 2 sekundžių.

Laikmačių iššūkis Nr. 1

Naudodamiesi tuo, ką išmokote iki šiol setTimeout, atspausdinkite šiuos 2 pranešimus po atitinkamo vėlavimo.

  • Po 4 sekundžių išspausdinkite pranešimą „ Sveiki po 4 sekundžių
  • Po 8 sekundžių išspausdinkite pranešimą „ Sveiki po 8 sekundžių “.

Apribojimai :

Savo sprendime galite apibrėžti tik vieną funkciją, kuri apima ir tiesiogines funkcijas. Tai reiškia, kad daugeliui setTimeoutskambučių teks naudoti tą pačią funkciją.

Sprendimas

Štai kaip išspręsčiau šį iššūkį:

// solution1.js const theOneFunc = delay => { console.log('Hello after ' + delay + ' seconds'); }; setTimeout(theOneFunc, 4 * 1000, 4); setTimeout(theOneFunc, 8 * 1000, 8);

Aš priverčiau theOneFuncgauti delayargumentą ir panaudojau šio delayargumento vertę atspausdintame pranešime. Tokiu būdu funkcija gali atspausdinti skirtingus pranešimus pagal bet kokią vėlavimo vertę, kurią jai perduodame.

Tada naudojau theOneFuncper du setTimeoutskambučius, vieną, kuris suveikė po 4 sekundžių, o kitą - po 8 sekundžių. Abu šie setTimeoutskambučiai taip pat gauna trečiąjį argumentą, nurodantį delayargumentą theOneFunc.

Vykdydami solution1.jsfailą naudodami nodekomandą, bus išspausdinti iššūkio reikalavimai, pirmasis pranešimas bus pateiktas po 4 sekundžių, o antrasis pranešimas - po 8 sekundžių.

Funkcijos vykdymo kartojimas

Ką daryti, jei aš paprašyčiau atspausdinti pranešimą kas 4 sekundes visam laikui?

Nors galite įdėti setTimeoutciklą, laikmačių API setIntervaltaip pat siūlo funkciją, kuri įvykdytų reikalavimą ką nors daryti visam laikui.

Štai „setInterval“ pavyzdys:

// example3.js setInterval( () => console.log('Hello every 3 seconds'), 3000 );

Šis pavyzdys atspausdins pranešimą kas 3 sekundes. Vykdant example3.jssu nodekomanda leis Mazgas spausdinti šį pranešimą amžinai, kol nužudyti procesas (su Ctrl + C ).

Laikmačių atšaukimas

Kadangi laikmačio funkcijos iškvietimas suplanuoja veiksmą, tą veiksmą taip pat galima atšaukti prieš jį vykdant.

Skambinimas setTimeoutgrąžina laikmačio „ID“ ir galite naudoti tą laikmačio ID kartu su clearTimeoutskambučiu, kad atšauktumėte tą laikmatį. Štai pavyzdys:

// example4.js const timerId = setTimeout( () => console.log('You will not see this one!'), 0 ); clearTimeout(timerId);

Šis paprastas laikmatis turėtų suveikti po 0ms (todėl tuoj pat), bet jis nebus, nes mes užfiksuojame timerIdvertę ir atšaukiame ją iškart po clearTimeoutskambučio.

Kai vykdysime example4.jssu nodekomanda, mazgas nieko nespausdins, o procesas tiesiog išeis.

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.

Jei dar tik pradedate mokytis „Node.js“, neseniai „Pluralsight“ paskelbiau pirmųjų žingsnių kursą , patikrinkite: