Acum câteva săptămâni, am trimis pe Twitter această întrebare din interviu:

*** Răspundeți la întrebarea din capul dvs. acum înainte de a continua ***

Aproximativ jumătate din răspunsurile la Tweet au fost greșite. Raspunsul este NU V8 (sau alte VM-uri) !! În timp ce este cunoscut sub numele de „JavaScript Timers”, funcții precum setTimeout și setInterval nu fac parte din specificațiile ECMAScript sau din nicio implementare a motorului JavaScript. Funcțiile cronometrului sunt implementate de browsere și implementările lor vor fi diferite între diferite browsere. Temporizatoarele sunt, de asemenea, implementate nativ de runtime-ul Node.js în sine.

În browsere, funcțiile principale ale temporizatorului fac parte din Window interfață, care are câteva alte funcții și obiecte. Interfața respectivă face ca toate elementele sale să fie disponibile la nivel global în domeniul principal JavaScript. Acesta este motivul pentru care puteți executa setTimeout direct în consola browserului.

În Node, temporizatoarele fac parte din global obiect, care se comportă similar cu browserul Window interfață. Puteți vedea codul sursă al temporizatoarelor în nod aici.

Unii ar putea crede că aceasta este o întrebare proastă a interviului – de ce să știi asta oricum ?! În calitate de dezvoltator JavaScript, cred că vă așteptați să știți acest lucru, deoarece dacă nu știți, ar putea fi un semn că nu înțelegeți complet modul în care V8 (și alte VM-uri) interacționează cu browserele și Node.

Să facem câteva exemple și provocări cu privire la funcțiile temporizatorului, nu-i așa?

Actualizați: Acest articol face acum parte din „Introducere completă la Node.js”.
Puteți citi versiunea actualizată a acestuia la aici.

Întârzierea executării unei funcții

Funcțiile cu temporizator sunt funcții de ordin superior care pot fi utilizate pentru a întârzia sau repeta executarea altor funcții (pe care le primesc ca prim argument).

Iată un exemplu despre întârziere:

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

Acest exemplu folosește setTimeout pentru a întârzia imprimarea mesajului de salut cu 4 secunde. Al doilea argument pentru setTimeout este întârzierea (în ms). Acesta este motivul pentru care am înmulțit 4 cu 1000 pentru a face în 4 secunde.

Primul argument pentru setTimeout este funcția a cărei execuție va fi întârziată.

Dacă executați example1.js fișier cu node comandă, Node se va întrerupe timp de 4 secunde și apoi va imprima mesajul de salut (și va ieși după aceea).

Rețineți că primul argument pentru setTimeout este doar o funcție referinţă. Nu trebuie să fie o funcție în linie ca și cum example1.js are. Iată același exemplu fără a utiliza o funcție inline:

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

Argumente trecătoare

Dacă funcția care folosește setTimeout pentru a întârzia execuția acceptă orice argumente, putem folosi argumentele rămase pentru setTimeout însuși (după cele 2 de care am aflat până acum) să retransmită valorile argumentului către funcția întârziată.

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

Iată un exemplu:

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

rocks funcția de mai sus, care este întârziată cu 2 secunde, acceptă a who argumentul și setTimeout apelul transmite valoarea „Node.js”Ca asta who argument.

Executarea example2.js cu node comanda va imprima „Node.js roci”După 2 secunde.

Timers Challenge # 1

Folosind ceea ce ai învățat până acum setTimeout, tipăriți următoarele 2 mesaje după întârzierile corespunzătoare.

  • Tipăriți mesajul „Bună ziua după 4 secunde”După 4 secunde
  • Tipăriți mesajul „Bună ziua după 8 secunde”După 8 secunde.

Constrângeri:
Puteți defini doar o singură funcție în soluția dvs., care include funcții inline. Aceasta înseamnă mulți setTimeout apelurile vor trebui să utilizeze exact aceeași funcție.

Soluţie

Iată cum aș rezolva această provocare:

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

am facut theOneFunc primi o delay argument și a folosit valoarea acelui delay argument în mesajul tipărit. În acest fel, funcția poate imprima diferite mesaje pe baza oricărei valori de întârziere pe care i-o transmitem.

Am folosit apoi theOneFunc in doi setTimeout apeluri, unul care se declanșează după 4 secunde și altul care se declanșează după 8 secunde. Amandoua setTimeout apelurile primesc și un A treia argument pentru a reprezenta delay argument pentru theOneFunc.

Executarea solution1.js fișier cu node comanda va imprima cerințele provocării, primul mesaj după 4 secunde și al doilea mesaj după 8 secunde.

Repetarea executării unei funcții

Ce se întâmplă dacă v-aș cere să imprimați un mesaj la fiecare 4 secunde, pentru totdeauna?

În timp ce poți pune setTimeout într-o buclă, API-ul cronometrelor oferă setInterval funcția de asemenea, care ar îndeplini cerința de a face ceva pentru totdeauna.

Iată un exemplu de setInterval:

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

Acest exemplu va imprima mesajul său la fiecare 3 secunde. Executarea example3.js cu node comanda va face ca Node să tipărească acest mesaj pentru totdeauna, până când ucideți procesul (cu CTRL + C).

Anularea cronometrelor

Deoarece apelarea unei funcții de temporizare programează o acțiune, acțiunea respectivă poate fi anulată și înainte de a fi executată.

Un apel către setTimeout returnează un „ID” al temporizatorului și puteți utiliza acel ID al temporizatorului cu un clearTimeout apel pentru a anula acel cronometru. Iată un exemplu:

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

Acest cronometru simplu ar trebui să se declanșeze după 0 ms (făcându-l imediat), dar nu va fi pentru că capturăm timerId valoare și anularea imediat după un clearTimeout apel.

Când executăm example4.js cu node comandă, Node nu va imprima nimic și procesul va ieși.

Apropo, în Node.js, există un alt mod de a face setTimeout cu 0 Domnișoară. API-ul temporizator Node.js are o altă funcție numită setImmediate, și este practic același lucru ca un setTimeout cu 0 ms, dar nu trebuie să specificăm o întârziere acolo:

setImmediate(
  () => console.log('I am equivalent to setTimeout with 0 ms'),
);

setImmediate funcţie nu este disponibil în toate browserele. Nu-l utilizați pentru codul front-end.

La fel ca clearTimeout, este deasemenea o clearInterval funcție, care face același lucru, dar pentru setInerval apeluri și există, de asemenea, un clearImmediate suna la fel.

O întârziere temporizată nu este un lucru garantat

În exemplul anterior, ați observat cum se execută ceva setTimeout după 0 ms nu a însemnat executarea imediată (după linia setTimeout), ci mai degrabă executarea imediată după orice altceva din script (inclusiv apelul clearTimeout)?

Permiteți-mi să clarific acest punct cu un exemplu. Iată un simplu setTimeout apel care ar trebui să se declanșeze după o jumătate de secundă, dar nu va:

// example5.js
setTimeout(
  () => console.log('Hello after 0.5 seconds. MAYBE!'),
  500,
);
for (let i = 0; i < 1e10; i++) {
  // Block Things Synchronously
}

Imediat după definirea temporizatorului în acest exemplu, blocăm timpul de rulare sincron cu un mare for buclă. 1e10 este 1 cu 10 zerouri în fața ei, deci bucla este a 10 Bucla de miliarde de căpușe (care simulează practic un procesor ocupat). Nodul nu poate face nimic în timp ce această buclă se bifează.

Desigur, acest lucru este foarte rău de făcut în practică, dar vă va ajuta aici să înțelegeți acest lucru setTimeout întârzierea nu este un lucru garantat, ci mai degrabă un minim lucru. 500 ms înseamnă o întârziere minimă de 500 Domnișoară. În realitate, scenariul va dura mult mai mult pentru a imprima linia de salut. Va trebui să aștepte bucla de blocare pentru a termina mai întâi.

Timers Challenge # 2

Scrieți un script pentru a imprima mesajul „Salut Lume”În fiecare secundă, dar doar de 5 ori. După 5 ori, scriptul ar trebui să imprime mesajul „Terminat”Și lăsați procesul Node să iasă.

Constrângeri: Nu puteți utiliza un setTimeout apel pentru această provocare.
Aluzie: Ai nevoie de un tejghea.

Soluţie

Iată cum aș rezolva acest lucru:

let counter = 0;
const intervalId = setInterval(() => {
  console.log('Hello World');
  counter += 1;
if (counter === 5) {
    console.log('Done');
    clearInterval(intervalId);
  }
}, 1000);

Am inițiat un counter valoare ca 0 și apoi a început un setInterval apel capturând id-ul său.

Funcția întârziată va imprima mesajul și va incrementa contorul de fiecare dată. În interiorul funcției întârziate, un if declarația va verifica dacă suntem la 5 ori de acum. Dacă da, se va tipări „Terminat”Și ștergeți intervalul folosind capturat intervalId constant. Întârzierea intervalului este 1000 Domnișoară.

Cine „apelează” exact funcțiile întârziate?

Când utilizați JavaScript this cuvânt cheie în interiorul unei funcții obișnuite, astfel:

function whoCalledMe() {
  console.log('Caller is', this);
}

Valoarea din interiorul this cuvântul cheie va reprezenta apelant a funcției. Dacă definiți funcția de mai sus într-un nod REPL, apelantul va fi global obiect. Dacă definiți funcția din consola unui browser, apelantul va fi window obiect.

Să definim funcția ca o proprietate pe un obiect pentru a face acest lucru un pic mai clar:

const obj = { 
  id: '42',
  whoCalledMe() {
    console.log('Caller is', this);
  }
};
// The function reference is now: obj.whoCallMe

Acum, când suni la obj.whoCallMe funcția folosind direct referința, apelantul va fi obj obiect (identificat prin id-ul său):

Cronometre JavaScript tot ce trebuie sa stiti

Acum, întrebarea este: ce ar fi apelantul dacă trecem referința la obj.whoCallMe la o setTimetout apel?

// What will this print??
setTimeout(obj.whoCalledMe, 0);

Cine va fi apelantul în acest caz?

Răspunsul este diferit în funcție de locul în care este executată funcția timer. Pur și simplu nu puteți depinde de cine este apelantul în acest caz. Pierdeți controlul apelantului, deoarece implementarea temporizatorului va fi cea care vă invocă funcția acum. Dacă îl testați într-un nod REPL, veți primi un Timetout obiect ca apelant:

1611588488 835 Cronometre JavaScript tot ce trebuie sa stiti

Rețineți că acest lucru contează numai dacă utilizați JavaScript this cuvânt cheie în funcțiile obișnuite. Nu trebuie să vă faceți griji deloc despre apelant dacă utilizați funcții săgeată.

Provocarea Timers # 3

Scrieți un script pentru a imprima continuu mesajul „Salut Lume”Cu întârzieri variate. Începeți cu o întârziere de 1 secundă și apoi incrementați întârzierea cu 1 secundă de fiecare dată. A doua oară va avea o întârziere de 2 secunde. A treia oară va avea o întârziere de 3 secunde și așa mai departe.

Includeți întârzierea în mesajul tipărit. Rezultatul preconizat arată ca:

Hello World. 1
Hello World. 2
Hello World. 3
...

Constrângeri: Puteți utiliza numai const pentru a defini variabile. Nu poți folosi let sau var.

Soluţie

Deoarece valoarea întârzierii este o variabilă în această provocare, nu o putem folosi setInterval aici, dar putem crea manual o execuție de interval folosind setTimeout în cadrul unui apel recursiv. Prima funcție executată cu setTimeout va crea un alt timer și așa mai departe.

De asemenea, deoarece nu putem folosi let / var, nu putem avea un contor pentru a incrementa întârzierea în fiecare apel recursiv, dar putem folosi în schimb argumentele funcției recursive pentru a crește în timpul apelului recursiv.

Iată o modalitate posibilă de a rezolva această provocare:

const greeting = delay =>
  setTimeout(() => {
    console.log('Hello World. ' + delay);
    greeting(delay + 1);
  }, delay * 1000);
greeting(1);

Timers Challenge # 4

Scrieți un script pentru a imprima continuu mesajul „Salut Lume”Cu același concept diferit de întârzieri ca provocarea nr. 3, dar de data aceasta, în grupuri de 5 mesaje pe interval de întârziere principală. Începând cu o întârziere de 100 ms pentru primele 5 mesaje, apoi o întârziere de 200 ms pentru următoarele 5 mesaje, apoi 300 ms și așa mai departe.

Iată cum ar trebui să se comporte scriptul:

  • În momentul de 100 ms, scriptul va începe tipărirea „Hello World” și va face acest lucru de 5 ori cu un interval de 100 ms. Primul mesaj va apărea la 100ms, al doilea mesaj la 200ms și așa mai departe.
  • După primele 5 mesaje, scriptul ar trebui să incrementeze întârzierea principală la 200 ms. Deci, al șaselea mesaj va fi tipărit la 500 ms + 200 ms (700 ms), al șaptelea mesaj va fi tipărit la 900 ms, al 8-lea mesaj va fi tipărit la 1100 ms și așa mai departe.
  • După 10 mesaje, scriptul ar trebui să incrementeze întârzierea principală la 300 ms. Deci, al 11-lea mesaj ar trebui să fie tipărit la 500ms + 1000ms + 300ms (18000ms). Al 12-lea mesaj ar trebui să fie tipărit la 21000 ms, și așa mai departe.
  • Continuați tiparul pentru totdeauna.

Includeți întârzierea în mesajul tipărit. Rezultatul așteptat arată astfel (fără comentarii):

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
...

Constrângeri: Puteți utiliza numai setInterval apeluri (nu setTimeout) și puteți utiliza doar instrucțiunea ONE if.

Soluţie

Pentru că nu putem folosi decât setInterval apeluri, vom avea nevoie și de recursivitate aici pentru a crește întârzierea următoarei setInterval apel. În plus, avem nevoie de o instrucțiune if pentru a controla acest lucru numai după 5 apeluri ale acelei funcții recursive.

Iată o soluție posibilă:

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);

Mulțumesc pentru lectură.

Dacă abia începi să înveți Node.js, am publicat recent un cursul primilor pași la Pluralsight, verifică:

1611588489 577 Cronometre JavaScript tot ce trebuie sa stiti
https://jscomplete.com/c/nodejs-getting-started