Het uitvoeringsmodel van JavaScript is genuanceerd en gemakkelijk verkeerd te begrijpen. Leren over de gebeurtenislus in de kern kan helpen.

JavaScript is een taal met één thread, gebouwd om taken één voor één af te handelen. Met de gebeurtenislus kan JavaScript echter asynchroon gebeurtenissen en callbacks afhandelen door gelijktijdige programmeersystemen te emuleren. Dit verzekert de prestaties van uw JavaScript-applicaties.

Wat is de JavaScript-gebeurtenisloop?

De gebeurtenislus van JavaScript is een mechanisme dat op de achtergrond van elke JavaScript-toepassing wordt uitgevoerd. Hiermee kan JavaScript taken in volgorde afhandelen zonder de hoofduitvoeringsthread te blokkeren. Dit wordt genoemd asynchrone programmering.

De gebeurtenislus houdt een wachtrij bij met taken die moeten worden uitgevoerd en voert die taken naar rechts web-API om één voor één uit te voeren. JavaScript houdt deze taken bij en handelt ze allemaal af op basis van het complexiteitsniveau van de taak.

De noodzaak begrijpen van de JavaScript-gebeurtenislus en asynchrone programmering. U moet begrijpen welk probleem het in wezen oplost.

Neem bijvoorbeeld deze code:

functionlongRunningFunction() {
// This function does something that takes a long time to execute.
for (var i = 0; i < 100000; i++) {
console.log("Hello")
}
}

functionshortRunningFunction(a) {
return a * 2 ;
}

functionmain() {
var startTime = Date.now();
longRunningFunction();

var endTime = Date.now();

// Prints the amount of time it took to execute functions
console.log(shortRunningFunction(2));
console.log("Time taken: " + (endTime - startTime) + " milliseconds");
}

main();

Deze code definieert eerst een functie genaamd longRunningFunction(). Deze functie voert een complexe, tijdrovende taak uit. In dit geval voert het een uit voor lus die meer dan 100.000 keer wordt herhaald. Dit betekent dat console.log("Hallo") loopt 100.000 keer over.

Afhankelijk van de snelheid van de computer kan dit lang duren en blokkeren shortRunningFunction() vanaf onmiddellijke uitvoering totdat de vorige functie is voltooid.

Voor de context is hier een vergelijking van de tijd die nodig is om beide functies uit te voeren:

En dan de single shortRunningFunction():

Het verschil tussen een bewerking van 2.351 milliseconden en een bewerking van 0 milliseconden is duidelijk wanneer u een performante app wilt bouwen.

Hoe de Event Loop helpt bij app-prestaties

De gebeurtenislus heeft verschillende stadia en onderdelen die ertoe bijdragen dat het systeem werkt.

De oproepstapel

De JavaScript-aanroepstack is essentieel voor de manier waarop JavaScript omgaat met functie- en gebeurtenisaanroepen vanuit uw toepassing. JavaScript-code wordt van boven naar beneden gecompileerd. Echter, Node.js, bij het lezen van de code, zal Node.js functieaanroepen van onder naar boven toewijzen. Terwijl het leest, duwt het de gedefinieerde functies als frames één voor één in de call-stack.

De call-stack is verantwoordelijk voor het handhaven van de uitvoeringscontext en de juiste volgorde van functies. Het doet dit door te werken als een LIFO-stack (Last-In-First-Out).

Dit betekent dat het laatste functieframe dat uw programma op de call-stack duwt, de eerste is die van de stapel springt en wordt uitgevoerd. Dit zorgt ervoor dat JavaScript de juiste volgorde van functie-uitvoering behoudt.

JavaScript haalt elk frame van de stapel totdat het leeg is, wat betekent dat alle functies zijn voltooid.

Libuv Web-API

De kern van de asynchrone programma's van JavaScript is libuv. De libuv-bibliotheek is geschreven in de programmeertaal C, die kan communiceren met die van het besturingssysteem low-level API's. De bibliotheek biedt verschillende API's waarmee JavaScript-code parallel met andere kan worden uitgevoerd code. API's voor het maken van threads, een API voor communicatie tussen threads en een API voor het beheren van threadsynchronisatie.

Bijvoorbeeld wanneer u gebruikt setTimeout in Node.js om de uitvoering te onderbreken. De timer wordt ingesteld via libuv, dat de gebeurtenislus beheert om de callback-functie uit te voeren zodra de opgegeven vertraging is verstreken.

Evenzo, wanneer u netwerkbewerkingen asynchroon uitvoert, verwerkt libuv die bewerkingen in een niet-blokkerende manier, zodat andere taken kunnen worden verwerkt zonder te wachten op de invoer/uitvoer (I/O)-bewerking einde.

De terugbel- en gebeurteniswachtrij

De callback & event-wachtrij is waar callback-functies wachten op uitvoering. Wanneer een asynchrone bewerking vanuit de libuv is voltooid, wordt de bijbehorende callback-functie aan deze wachtrij toegevoegd.

Hier is hoe de volgorde gaat:

  1. JavaScript verplaatst asynchrone taken naar libuv zodat het deze kan afhandelen, en gaat onmiddellijk door met het afhandelen van de volgende taak.
  2. Wanneer de asynchrone taak is voltooid, voegt JavaScript zijn callback-functie toe aan de callback-wachtrij.
  3. JavaScript blijft andere taken in de call-stack uitvoeren totdat het klaar is met alles in de huidige volgorde.
  4. Zodra de call-stack leeg is, kijkt JavaScript naar de callback-wachtrij.
  5. Als er een callback in de wachtrij staat, wordt de eerste op de call-stack geplaatst en uitgevoerd.

Op deze manier blokkeren asynchrone taken de hoofdthread niet en zorgt de callback-wachtrij ervoor dat hun corresponderende callbacks worden uitgevoerd in de volgorde waarin ze zijn voltooid.

De Event Loop-cyclus

De gebeurtenislus heeft ook iets dat de microtaakwachtrij wordt genoemd. Deze speciale wachtrij in de gebeurtenislus bevat microtaken die moeten worden uitgevoerd zodra de huidige taak in de call-stack is voltooid. Deze uitvoering vindt plaats vóór de volgende weergave of iteratie van de gebeurtenislus. Microtaken zijn taken met hoge prioriteit die voorrang hebben op reguliere taken in de gebeurtenislus.

Bij het werken met Promises wordt meestal een microtaak gemaakt. Telkens wanneer een belofte wordt opgelost of afgewezen, komt deze overeen .Dan() of .vangst() callbacks voegt zich bij de microtask-wachtrij. U kunt die wachtrij gebruiken om taken te beheren die onmiddellijk moeten worden uitgevoerd na de huidige bewerking, zoals het bijwerken van de gebruikersinterface van uw toepassing of het afhandelen van statuswijzigingen.

Bijvoorbeeld een webtoepassing die gegevens ophaalt en de gebruikersinterface bijwerkt op basis van de opgehaalde gegevens. Gebruikers kunnen dit ophalen van gegevens activeren door herhaaldelijk op een knop te klikken. Elke klik op de knop initieert een asynchrone bewerking voor het ophalen van gegevens.

Zonder microtasks zou de gebeurtenislus voor deze taak als volgt werken:

  1. De gebruiker klikt herhaaldelijk op de knop.
  2. Elke klik op de knop activeert een asynchrone bewerking voor het ophalen van gegevens.
  3. Naarmate de bewerkingen voor het ophalen van gegevens zijn voltooid, voegt JavaScript de bijbehorende callbacks toe aan de normale taakwachtrij.
  4. De gebeurtenislus begint met het verwerken van taken in de reguliere takenwachtrij.
  5. De UI-update op basis van de resultaten van het ophalen van gegevens wordt uitgevoerd zodra de normale taken dit toelaten.

Bij microtaken werkt de gebeurtenislus echter anders:

  1. De gebruiker klikt herhaaldelijk op de knop en activeert een asynchrone gegevensophaalbewerking.
  2. Naarmate de bewerkingen voor het ophalen van gegevens zijn voltooid, voegt de gebeurtenislus hun corresponderende callbacks toe aan de microtask-wachtrij.
  3. De gebeurtenislus begint onmiddellijk na het voltooien van de huidige taak (klik op de knop) met het verwerken van taken in de microtaakwachtrij.
  4. De UI-update op basis van de resultaten van het ophalen van gegevens wordt uitgevoerd vóór de volgende reguliere taak, waardoor een meer responsieve gebruikerservaring wordt geboden.

Hier is een codevoorbeeld:

const fetchData = () => {
returnnewPromise(resolve => {
setTimeout(() => resolve('Data from fetch'), 2000);
});
};

document.getElementById('fetch-button').addEventListener('click', () => {
fetchData().then(data => {
// This UI update will run before the next rendering cycle
updateUI(data);
});
});

In dit voorbeeld wordt elke klik op de knop "Ophalen" aangeroepen gegevens ophalen(). Elke bewerking voor het ophalen van gegevens wordt gepland als een microtaak. Op basis van de opgehaalde gegevens wordt de UI-update uitgevoerd onmiddellijk nadat elke ophaalbewerking is voltooid, vóór enige andere rendering- of gebeurtenislustaken.

Dit zorgt ervoor dat gebruikers de bijgewerkte gegevens zien zonder enige vertraging als gevolg van andere taken in de gebeurtenislus.

Het gebruik van microtasks in dergelijke scenario's kan UI-jank voorkomen en zorgen voor snellere en soepelere interacties in uw toepassing.

Implicaties van de Event Loop voor webontwikkeling

Het begrijpen van de gebeurtenislus en het gebruik van de functies ervan is essentieel voor het bouwen van performante en responsieve applicaties. De gebeurtenislus biedt asynchrone en parallelle mogelijkheden, zodat u complexe taken in uw toepassing efficiënt kunt afhandelen zonder de gebruikerservaring in gevaar te brengen.

Node.js biedt alles wat je nodig hebt, inclusief webwerkers om verdere parallelliteit te bereiken buiten de rode draad van JavaScript.