Hur fungerar Event Loop i JavaScript?

Även om det kan kräva en djupgående förståelse av språk som C++ och C för att skriva fullskalig produktionskod, kan JavaScript ofta skrivas med bara en grundläggande förståelse för vad som kan göras med språket.

Koncept, som att skicka tillbaka callbacks till funktioner eller skriva asynkron kod, är ofta inte så svåra att implementera, vilket gör att de flesta JavaScript-utvecklare bryr sig mindre om vad som händer under huven. De bryr sig helt enkelt inte om att förstå komplexiteten som djupt har abstraherat från dem av språket.

Som JavaScript-utvecklare blir det allt viktigare att förstå vad som verkligen händer under huven och hur de flesta av dessa komplexiteter som abstraheras från oss verkligen fungerar. Det hjälper oss att fatta mer välgrundade beslut, vilket i sin tur kan öka vår kodprestanda drastiskt.

Den här artikeln fokuserar på ett av de mycket viktiga men sällan förstådda begreppen eller termerna i JavaScript. EVENT LOOP!.

Att skriva asynkron kod kan inte undvikas i JavaScript, men varför betyder en kod som körs asynkront egentligen? dvs. Event Loop

Innan vi kan förstå hur eventloopen fungerar måste vi först förstå vad JavaScript i sig är och hur det fungerar!

Vad är JavaScript?

Innan vi fortsätter vill jag att vi tar ett steg tillbaka till grunderna. Vad är JavaScript egentligen? Vi skulle kunna definiera JavaScript som;

JavaScript är ett tolkat, enkelgängat, icke-blockerande, asynkront, samtidig språk på hög nivå.

Vänta, vad är det här? En bokaktig definition? 🤔

Låt oss bryta ner det!

Nyckelorden här när det gäller den här artikeln är enkeltrådiga, icke-blockerande, samtidiga och asynkrona.

Enkel tråd

En exekveringstråd är den minsta sekvensen av programmerad instruktion som kan hanteras oberoende av en schemaläggare. Ett programmeringsspråk är entrådat, vilket innebär att det bara kan utföra en uppgift eller operation åt gången. Detta innebär att det skulle köra en hel process från början till slut utan att tråden avbryts eller stoppas.

Till skillnad från flertrådade språk där flera processer kan köras på flera trådar samtidigt utan att blockera varandra.

Hur kan JavaScript vara entrådigt och icke-blockerande på samma gång?

Men vad betyder blockering?

Icke-blockerande

Det finns ingen definition av blockering; det betyder helt enkelt saker som går långsamt på tråden. Så icke-blockering betyder saker som inte är långsamma på tråden.

Men vänta, sa jag att JavaScript körs på en enda tråd? Och jag sa också att det inte blockerar, vilket innebär att uppgiften körs snabbt på samtalsstacken? Men hur??? Vad sägs om när vi kör timers? Slingor?

Koppla av! Det får vi reda på om en stund 😉.

Samverkande

Samtidighet betyder att koden exekveras samtidigt av mer än en tråd.

Okej, det börjar bli riktigt bra konstig nu, hur kan JavaScript vara entrådigt och samtidigt vara samtidigt? dvs exekvera dess kod med mer än en tråd?

Asynkron

Asynkron programmering innebär att koden körs i en händelseslinga. När det finns en blockeringsoperation startas händelsen. Blockeringskoden fortsätter att köras utan att blockera huvudexekveringstråden. När blockeringskoden slutar köras är den kön resultatet av blockeringsoperationerna och skjuter tillbaka dem till stacken.

Men JavaScript har en enda tråd? Vad exekverar då denna blockeringskod samtidigt som andra koder i tråden exekveras?

Innan vi fortsätter, låt oss ha en sammanfattning av ovanstående.

  • JavaScript är entrådigt
  • JavaScript är icke-blockerande, dvs långsamma processer blockerar inte dess exekvering
  • JavaScript är samtidigt, dvs det kör sin kod i mer än en tråd samtidigt
  • JavaScript är asynkront, dvs det kör blockerande kod någon annanstans.

Men ovanstående stämmer inte exakt, hur kan ett entrådigt språk vara icke-blockerande, samtidigt och asynkront?

Låt oss gå lite djupare, låt oss gå ner till JavaScript-runtime-motorerna, V8, kanske har den några dolda trådar som vi inte är medvetna om.

V8 motor

V8-motorn är en högpresterande, öppen källkods-körningsmotor för webbsammansättning för JavaScript skriven i C++ av Google. De flesta webbläsare kör JavaScript med V8-motorn, och även den populära node js runtime-miljön använder det också.

På enkel engelska är V8 ett C++-program som tar emot JavaScript-kod, kompilerar och kör den.

V8:an gör två viktiga saker;

  • Högminnestilldelning
  • Anropsstackkörningskontext

Tyvärr var vår misstanke felaktig. V8:an har bara en callstack, tänk på callstacken som tråden.

En tråd === en anropsstack === en exekvering åt gången.

Bild – Hacker Noon

Eftersom V8 bara har en anropsstack, hur körs då JavaScript samtidigt och asynkront utan att blockera huvudexekveringstråden?

Låt oss försöka ta reda på det genom att skriva en enkel men vanlig asynkron kod och analysera den tillsammans.

JavaScript kör varje kod rad för rad, en efter varandra (entrådig). Som väntat skrivs den första raden ut i konsolen här, men varför skrivs den sista raden ut före timeoutkoden? Varför väntar inte exekveringsprocessen på timeoutkoden (blockering) innan den går vidare för att köra den sista raden?

Någon annan tråd verkar ha hjälpt oss att exekvera den timeouten eftersom vi är ganska säkra på att en tråd bara kan utföra en enda uppgift vid vilken tidpunkt som helst.

Låt oss ta en tjuvtitt på V8 källkod ett tag.

Vänta, va??!!! Det finns inga timerfunktioner i V8, ingen DOM? Inga evenemang? Ingen AJAX?… Yeeeessss!!!

Händelser, DOM, timers, etc. är inte en del av JavaScripts kärnimplementering, JavaScript överensstämmer strikt med Ecma Scripts specifikationer och olika versioner av det hänvisas ofta till enligt dess Ecma Scripts Specifications (ES X).

Utförande arbetsflöde

Händelser, timers, Ajax-förfrågningar tillhandahålls alla på klientsidan av webbläsarna och kallas ofta för webb-API. Det är de som gör att den entrådade JavaScript-koden är icke-blockerande, samtidig och asynkron! Men hur?

Det finns tre huvudsektioner i exekveringsarbetsflödet för alla JavaScript-program, anropsstacken, webb-API:et och uppgiftskön.

Call Stack

En stack är en datastruktur där det senast tillagda elementet alltid är det första som tas bort från stacken, man kan tänka sig det som en stack av en plåt där endast den första plåten som lades till senast kan tas bort först. En Call Stack är helt enkelt ingenting annat än en stackdatastruktur där uppgifter eller kod exekveras i enlighet med detta.

Låt oss betrakta exemplet nedan;

Källa – https://youtu.be/8aGhZQkoFbQ

När du anropar funktionen printSquare() skjuts den till anropsstacken, funktionen printSquare() anropar square()-funktionen. Square()-funktionen skjuts in i stacken och anropar även multiply()-funktionen. Multipliceringsfunktionen skjuts in på stapeln. Eftersom multipliceringsfunktionen returnerar och är det sista som trycktes till stacken, löses den först och tas bort från stacken, följt av square()-funktionen och sedan printSquare()-funktionen.

Webb-API

Det är här kod som inte hanteras av V8-motorn exekveras för att inte ”blockera” huvudexekveringstråden. När anropsstacken stöter på en webb-API-funktion, överlämnas processen omedelbart till webb-API:t, där den exekveras och frigör anropsstacken för att utföra andra operationer under dess exekvering.

Låt oss gå tillbaka till vårt setTimeout-exempel ovan;

När vi kör koden, skjuts den första console.log-raden till stacken och vi får vår utdata nästan omedelbart, när vi kommer till timeout, hanteras timers av webbläsaren och är inte en del av V8:s kärnimplementering, den blir pushad till webb-API:t istället, vilket frigör stacken så att den kan utföra andra operationer.

Medan timeouten fortfarande är igång, går stacken vidare till nästa handlingsrad och kör den sista console.log, vilket förklarar varför vi får det utmatat före timerutgången. När timern är klar händer något. console.log in sedan timern dyker magiskt upp i samtalsstacken igen!

Hur?

Eventslingan

Innan vi diskuterar händelseslingan, låt oss först gå igenom funktionen för uppgiftskön.

Tillbaka till vårt timeout-exempel, när webb-API:et slutfört att utföra uppgiften, trycker det inte bara tillbaka till samtalsstacken automatiskt. Den går till uppgiftskön.

En kö är en datastruktur som fungerar enligt Först in först ut-principen, så när uppgifter skjuts in i kön kommer de ut i samma ordning. Uppgifter som har utförts av webb-API:erna, som skjuts till uppgiftskön, gå sedan tillbaka till samtalsstacken för att få resultatet utskrivet.

Men vänta. VAD FAN ÄR HÄNDELSEN LOOP???

Källa – https://youtu.be/8aGhZQkoFbQ

Händelseloopen är en process som väntar på att samtalsstacken ska vara klar innan återuppringningar skickas från uppgiftskön till samtalsstacken. När stapeln är ren, utlöses händelseslingan och kontrollerar uppgiftskön för tillgängliga återuppringningar. Om det finns några, skjuter den den till samtalsstacken, väntar på att samtalsstacken ska bli ren igen och upprepar samma process.

Källa – https://www.quora.com/How-does-an-event-loop-work/answer/Timothy-Maxwell

Diagrammet ovan visar det grundläggande arbetsflödet mellan händelseslingan och uppgiftskön.

Slutsats

Även om detta är en mycket grundläggande introduktion, ger konceptet med asynkron programmering i JavaScript tillräckligt med insikt för att tydligt förstå vad som händer under huven och hur JavaScript kan köras samtidigt och asynkront med bara en enda tråd.

JavaScript är alltid on-demand, och om du är nyfiken på att lära dig, skulle jag råda dig att kolla in detta Udemy kurs.