JavaScript-gebeurtenislus uitgelegd: Oproepstapel, microtaken en timers met voorbeelden
Waarom duurt een setTimeout van nul nog steeds? Hoe kan één draad zoveel tegelijk verwerken? Ontdek hoe de gebeurtenislus echt werkt, test deze met de code die we hebben uitgevoerd en los de klassieke interviewvraag op.
- JavaScript voert uw code uit op een enkele thread, stuk voor stuk.
- Als de call-stack leeg is, worden eerst alle microtaken uitgevoerd, en daarna één taak.
setTimeout(fn, 0)betekent “zo snel mogelijk na een minimale vertraging”, niet “nu”.- Lang synchroon werk blokkeert alles, dus splits het op of verplaats het naar een medewerker.
Voer een paar regels uit met setTimeout en een belofte, en de uitvoervolgorde zal u misschien verrassen. De JavaScript-gebeurtenislus legt uit waarom. Het is het mechanisme waarmee een taal met één thread timers, netwerkoproepen en klikken kan verwerken zonder te bevriezen.
Als je het eenmaal begrijpt, begint een hele reeks verwarrende bugs logisch te worden. We hebben elk voorbeeld uitgevoerd op Node.js 24 en de uitvoer komt uit die run. De belangrijkste bestelregels zijn ook van toepassing in browsers, en we wijzen erop waar Node.js verschilt. U zult hier meer uit halen als u de basisprincipes van JavaScript-beloften al kent.
Waarom de gebeurtenislus bestaat
JavaScript voert uw code uit op één hoofdthread, zodat er slechts één deel tegelijk wordt uitgevoerd. Als er simpelweg op een netwerkverzoek werd gewacht, zou een pagina vastlopen totdat het antwoord terugkwam.
JavaScript wacht dus niet. Het trage werk wordt overgedragen aan de hostomgeving, dat wil zeggen de browser of Node.js, die het op de achtergrond afhandelt. Wanneer dat werk klaar is, wordt er teruggebeld in een wachtrij. De taak van de gebeurtenislus is om callbacks in de wachtrij naar de stapel te verplaatsen wanneer deze vrij is.
Denk aan een chef-kok die alleen werkt. De chef-kok zet een gerecht in de oven en stelt een timer in, in plaats van ernaar te staren. Als de timer afgaat, wordt de taak op een lijst gezet, en de chef-kok pakt deze op nadat hij klaar is met wat hij op dat moment in handen heeft.
De bewegende delen
| Deel | Wat het doet | Voorbeelden |
|---|---|---|
| Bel stapel | Houdt de momenteel actieve functies bij | Uw synchrone code |
| Host-API’s | Werk buiten uw code | Timers, fetch, bestand gelezen |
| Takenwachtrij | Houdt callbacks vast die klaar zijn om te worden uitgevoerd | setTimeout, klikken, I/O |
| Wachtrij voor microtaken | Houdt callbacks met hogere prioriteit vast | .then, await, queueMicrotask |
| Gebeurtenislus | Bepaalt wat er daarna gebeurt | Loopt continu |
Taken worden soms macrotaken genoemd. Strikt genomen kunnen er meerdere taakbronnen zijn, en de exacte details verschillen per omgeving.
De regels
- Voer alle synchrone code uit totdat de call-stack leeg is.
- Voer alle microtaken uit, inclusief eventuele nieuwe die onderweg worden toegevoegd.
- Geef in een browser een update weer als deze nodig is.
- Neem één taak uit een wachtrij en voer deze uit.
- Ga terug naar stap 2.
Het belangrijkste detail: alle microtaken, daarna één taak. De meeste gebeurtenisluspuzzels worden opgelost door die ene regel toe te passen.
Zie de bestelling
console.log("1 sync");
setTimeout(() => console.log("5 timeout"), 0);
Promise.resolve().then(() => console.log("3 promise"));
queueMicrotask(() => console.log("4 microtask"));
console.log("2 sync");
// 1 sync
// 2 sync
// 3 promise
// 4 microtask
// 5 timeout
Dit is wat er gebeurde. De twee console.log lijnen zijn synchroon, dus ze liepen als eerste. De belofte callback en queueMicrotask gingen naar de microtask-wachtrij en werden uitgevoerd zodra de stapel leeg was, in de volgorde waarin ze werden toegevoegd. De terugbeltimer moest wachten op een taakbeurt, ook al was de vertraging nul.
Microtaken worden uitgevoerd tussen timers
Na elke afzonderlijke taak wordt de wachtrij voor microtaken weer geleegd. Dit voorbeeld bewijst het:
setTimeout(() => {
console.log("timer 1");
Promise.resolve().then(() => console.log("promise inside timer 1"));
}, 0);
setTimeout(() => console.log("timer 2"), 0);
// timer 1
// promise inside timer 1
// timer 2
De belofte, die tijdens de eerste timer tot stand kwam, liep vóór de tweede timer. Moderne browsers en Node.js 11 en later gedragen zich op deze manier.
Waar wachten past
await splitst een functie in tweeën. Alles ervoor loopt synchroon, en alles erna is gepland als een microtaak. Dit verbaast veel mensen:
async function task() {
console.log("A before await");
await null;
console.log("C after await");
}
task();
console.log("B after calling task");
// A before await
// B after calling task
// C after await
Een async-functie start niet op de achtergrond. Het loopt op dezelfde draad totdat het zijn eerste await bereikt. Dat geldt zelfs als je wacht op iets dat geen echte belofte is, zoals null. Zie onze JavaScript async/await-handleiding voor meer informatie over deze syntaxis.
Het blokkeren van de lus
Terwijl synchrone code wordt uitgevoerd, kan niets anders dat doen. Om dit te laten zien, hebben we een timer ingesteld op 0 ms en de thread vervolgens een halve seconde bezig gehouden:
const start = Date.now();
setTimeout(() => console.log("timer ran after", Date.now() - start, "ms"), 0);
while (Date.now() - start < 500) {}
// timer ran after about 500 ms
De vertraging van de timer is een minimum en is geen garantie. Het liep alleen toen de stapel vrijkwam. Op een webpagina betekent een lange lus als deze dat klikken niet reageren en animaties stoppen. Op een server moet elk ander verzoek wachten. Daarom kan één langzame synchrone oproep alle gebruikers van een Node.js-proces schade toebrengen.
Veelvoorkomende oorzaken van blokkering zijn enorme lussen, het parseren van zeer grote JSON, synchrone bestandsfuncties zoals readFileSync in verzoekhandlers, en catastrofale reguliere expressies.
Tip: In Chrome DevTools markeert het prestatiepaneel taken die langer dan 50 ms duren als lange taken. Het is een snelle manier om erachter te komen wat ervoor zorgt dat een pagina traag aanvoelt.
Responsief blijven
Geef de evenementenlus bij zwaar werk de kans om te ademen. Eén manier is om af en toe een timer te pauzeren, waardoor andere taken kunnen worden uitgevoerd:
const items = [0, 1, 2, 3, 4];
async function processAll() {
for (const item of items) {
if (item === 2) await new Promise((r) => setTimeout(r, 0));
console.log("item", item);
}
}
setTimeout(() => console.log("other work gets a turn"), 0);
processAll();
// item 0
// item 1
// other work gets a turn
// item 2
// item 3
// item 4
De andere timer liep in het midden, omdat we toegaven met een echte taak. Echte code zou om de zoveel items of milliseconden opleveren. Voor echt CPU-zwaar werk, zoals beeldverwerking, gebruikt u een Web Worker in browsers of een werkthread in Node.js, die op afzonderlijke threads draaien.
Uithongering door microtaken
Omdat de lus de wachtrij voor microtaken volledig leegmaakt voordat verder wordt gegaan, kan een microtaak die steeds meer taken plant, al het andere tegenhouden. We hebben dit getest met 100.000 gekoppelde microtaken:
let count = 0;
let timerFired = false;
setTimeout(() => { timerFired = true; }, 0);
function spin() {
count++;
if (count < 100000) queueMicrotask(spin);
else console.log("timer fired during microtasks?", timerFired);
}
queueMicrotask(spin);
// timer fired during microtasks? false
De timer kon pas lopen als ze allemaal klaar waren. Als die keten nooit zou eindigen, zouden timers nooit worden geactiveerd, en in een browser zou de pagina nooit opnieuw worden geschilderd. Merk op dat toegeven met await aan een opgeloste belofte hier niet helpt, omdat het alleen maar een extra microtaak toevoegt. Je hebt een echte taak nodig, zoals een timer, om anderen een beurt te geven.
Renderen in browsers
Een browser krijgt meestal de kans om tussen taken door te schilderen. Dat verklaart een veel voorkomende bug:
spinner.hidden = false;
doHeavyWork(); // blocks the thread
spinner.hidden = true;
De spinner verschijnt nooit, omdat de browser geen mogelijkheid had om tussen die regels weer te geven. Om het probleem op te lossen, laat u een frame passeren voordat het zware werk begint, of verplaatst u dat werk naar een arbeider. Voor code die beelden verandert, wordt requestAnimationFrame vlak voor de volgende verf uitgevoerd, waardoor dit het juiste hulpmiddel voor animatie is.
Node.js-verschillen
Node.js heeft zijn eigen gebeurtenislus met verschillende fasen, waaronder timers, I/O-polling en setImmediate. Het heeft ook process.nextTick, dat in zijn eigen wachtrij draait. Dit is wat we zagen toen we deze vanuit een asynchrone functie uitvoerden:
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
// promise
// nextTick
// immediate
// timeout
In veel artikelen wordt gesteld dat nextTick beloften altijd overtreft, en dat een timer beter is dan setImmediate. Ons resultaat laat zien dat het niet zo eenvoudig is. Onze code draaide al binnen een microtaak, dus de beloftewachtrij ging als eerste door. En het is bekend dat de volgorde van setTimeout en setImmediate varieert, afhankelijk van waar ze vandaan worden gebeld. Schrijf dus geen code die afhankelijk is van de exacte volgorde hiervan. Als het ene na het andere moet gebeuren, koppel ze dan expliciet aan elkaar.
Interviewquiz
Probeer de volgorde uit te werken voordat je het antwoord leest:
console.log("a");
setTimeout(() => console.log("b"), 0);
Promise.resolve().then(() => {
console.log("c");
setTimeout(() => console.log("d"), 0);
Promise.resolve().then(() => console.log("e"));
});
console.log("f");
Onze oplage drukte a, f, c, e, b, d. Dit is waarom:
aenfzijn synchroon, dus ze lopen als eerste.- De stapel is leeg, dus microtaken worden uitgevoerd:
c, daarna de nieuwe microtaake, in dezelfde ronde. - Nu één taak: timer
b, die als eerste in de wachtrij stond. - Dan de volgende taak: timer
d, die tijdens de microtaak in de wachtrij stond.
Veel voorkomende fouten
- Het denken
setTimeoutis exact. De vertraging is slechts een minimum. - Nadenken
asyncbetekent een nieuwe thread. Asynchrone code wordt tussen de wachttijden nog steeds op de hoofdthread uitgevoerd. - Het gebruik van sleeps om de bestelling te corrigeren. Het wachten op de daadwerkelijke toestand is veel betrouwbaarder, wat ook geldt voor slechte tests.
- Gebruik
forEachmetawait. Er wordt niet gewacht tussen items. Zie JavaScript-arraymethoden. - Zwaar synchroon werk doen in een serverhandler. Eén verzoek kan elk ander verzoek vertragen.
Tot slot
Onthoud de kernregel: voer de stapel uit totdat deze leeg is, voer alle microtaken uit, voer dan één taak uit en herhaal. Al het overige volgt daaruit. Om ervoor te zorgen dat het blijft hangen, schrijft u uw eigen versie van de bovenstaande quiz, voorspelt u de uitkomst op papier en voert u deze vervolgens uit. Als je er één fout maakt en begrijpt waarom, leer je meer dan welk diagram dan ook.
Veelgestelde vragen
Wat is de gebeurtenislus in JavaScript?
Het is het mechanisme dat callbacks in de wachtrij uitvoert wanneer de call-stack leeg is. Hiermee kan single-threaded JavaScript timers, invoer en netwerkreacties verwerken zonder te blokkeren.
Wat is het verschil tussen een microtaak en een taak?
Microtaken, zoals belofte-callbacks, worden allemaal uitgevoerd zodra de stapel leeg raakt. Taken, zoals timers, worden één voor één uitgevoerd, waarbij de microtaken na elke taak worden afgevoerd.
Waarom wordt setTimeout met 0 niet onmiddellijk uitgevoerd?
De terugbelopdracht moet wachten op een taakbeurt. Alle synchrone code en microtaken worden eerst uitgevoerd, en de vertraging is eerder een minimum dan een exacte tijd.
Is JavaScript single-threaded?
Uw belangrijkste JavaScript-code draait op één thread. Browsers en Node.js gebruiken andere threads voor wat achtergrondwerk, en met worker-rollen kun je code parallel uitvoeren.
Blokkeert await de gebeurtenislus?
Nee, wacht op pauzes, alleen die asynchrone functie, en andere code kan in de tussentijd worden uitgevoerd. Lange synchrone code ervoor of erna kan de lus echter nog steeds blokkeren.
Blijf leren
Hulp nodig met testen, WordPress of SEO?
We helpen teams betere software te leveren en hun sites te laten groeien. Vertel ons waar u mee bezig bent en wij stellen een duidelijke volgende stap voor.
Meer handleidingen over testen, AI en moderne ontwikkeling
Genoten van dit artikel? Kies een onderwerp en ga direct naar onze nieuwste tutorials.
AI-hallucinaties uitgelegd: waarom taalmodellen dingen verzinnen en hoe ze te verminderen
AI-hallucinaties uitgelegd: wat ze zijn, waarom taalmodellen ze produceren, veelvoorkomende typen, hoe je ze kunt verminderen met aarding en aanwijz…
Lees artikel →DevOpsOmgevingsvariabelen uitgelegd: .env-bestanden, geheimen en configuratie goed gedaan
Omgevingsvariabelen uitgelegd met Python-voorbeelden die we hebben uitgevoerd: ze lezen en instellen, waarom elke waarde een string is, .env-bestand…
Lees artikel →SoftwareontwikkelingAPI-snelheidsbeperking uitgelegd: algoritmen, headers en afhandeling van 429-fouten
API-snelheidsbeperking uitgelegd met een simulatie die we hebben uitgevoerd: token-bucket, vast venster, schuifvenster, 429 reacties, headers van sn…
Lees artikel →TestautomatiseringSpotten in Python: unittest.mock, patch en de fouten die tests doen liegen
Spotten in Python met unittest.mock, getest: Mock en return_value, side_effect, patchen waar een naam wordt gebruikt, spec en autospec, spotdatums e…
Lees artikel →SoftwaretestenCodedekking uitgelegd: lijn versus filiaaldekking en wat 100% werkelijk betekent
Codedekking uitgelegd met een echte dekking.py-run: dekking van lijnen, vertakkingen en condities, een rapport lezen, waarom 100% dekking nog steeds…
Lees artikel →Databases & SQLDELETE versus TRUNCATE versus DROP in SQL: verschillen, terugdraaien en wanneer elk te gebruiken
DELETE versus TRUNCATE versus DROP uitgelegd met geteste SQL: wat elk verwijdert, terugdraaien, triggers, identiteitsreset, snelheid en hoe PostgreS…
Lees artikel →Databases & SQLSQL-weergaven uitgelegd: creëer, gebruik en vermijd de valkuilen, met voorbeelden
SQL-weergaven uitgelegd met query's die we hebben uitgevoerd: een weergave maken, live resultaten, kolommen verbergen, prestaties, bijwerkbare en ge…
Lees artikel →SoftwareontwikkelingTypeScript-interface versus type: echte verschillen, getest met de compiler
TypeScript-interface versus type, gecontroleerd met de echte compiler: wat beide kunnen doen, wat slechts één kan doen, samenvoegen van declaraties,…
Lees artikel →SoftwareontwikkelingPython *args en **kwargs uitgelegd: flexibele functies met duidelijke voorbeelden
Python *args en **kwargs uitgelegd met code die we hebben uitgevoerd: in- en uitpakken, parametervolgorde, argumenten die alleen trefwoorden en posi…
Lees artikel →SoftwareontwikkelingPython Lambda-functies: syntaxis, echt gebruik en wanneer u ze niet moet gebruiken
Python lambda-functies uitgelegd met code die we hebben uitgevoerd: syntaxis, sorteren met sleutel, kaart en filter, sluitingen, de laat-bindende va…
Lees artikel →Kunstmatige intelligentieWat zijn AI-agenten? Hoe ze werken, met een eenvoudige agentloop die u kunt uitvoeren
Wat zijn AI-agenten? Een duidelijke gids voor de agentenlus, tools, geheugen en vangrails, met een kleine Python-lus die we hebben uitgevoerd, echte…
Lees artikel →DevOpsMonitoring versus waarneembaarheid: het echte verschil, met logboeken, statistieken en sporen
Monitoring versus waarneembaarheid uitgelegd: bekende en onbekende fouten, logboeken, statistieken en sporen, een walkthrough voor afrekenincidenten…
Lees artikel →DevOpsDocker-volumes versus bindingsmounts: bewaar containergegevens op de juiste manier
Docker-volumes versus bind-mounts uitgelegd: waarom containergegevens verdwijnen, benoemde volumes, bind-mounts, tmpfs, Compose-voorbeelden, back-up…
Lees artikel →TestautomatiseringTDD versus BDD: belangrijkste verschillen, één functie die op beide manieren is gebouwd en wanneer deze moet worden gebruikt
TDD versus BDD uitgelegd met één kortingsfunctie die in beide richtingen is gebouwd: rood-groen-refactor met code die we hebben uitgevoerd, gegeven-…
Lees artikel →SoftwaretestenGebruikersacceptatietesten (UAT): proces, checklist en echte voorbeelden
Gebruikersacceptatietesten uitgelegd: wie voert het uit, hoe het verschilt van QA, een proces in zeven stappen, een UAT-testcase-sjabloon, een check…
Lees artikel →Databases & SQLSQL NULL-waarden uitgelegd: IS NULL, COALESCE, NULLIF en de te vermijden vallen
SQL NULL-waarden uitgelegd met query's die we hebben uitgevoerd: logica met drie waarden, IS NULL, COALESCE, NULLIF, hoe aggregaten, joins en sorter…
Lees artikel →Databases & SQLSQL CASE WHEN-instructie: syntaxis, echte voorbeelden en veelvoorkomende fouten
SQL CASE WHEN uitgelegd met vragen die we hebben uitgevoerd: eenvoudige en doorzochte formulieren, volgorde van de voorwaarden, draaipunten met SUM,…
Lees artikel →SoftwareontwikkelingWat is JWT? JSON-webtokens uitgelegd met een werkend voorbeeld
Wat is JWT? JSON Web Tokens uitgelegd: de drie delen, een token gebouwd en waarmee is geknoeid in de code die we hebben uitgevoerd, JWT versus sessi…
Lees artikel →SoftwareontwikkelingJavaScript-gebeurtenislus uitgelegd: call-stack, microtaken en timers met voorbeelden
De JavaScript-gebeurtenislus uitgelegd met code die we hebben uitgevoerd: call-stack, taak- en microtask-wachtrijen, waarom setTimeout 0 niet onmidd…
Lees artikel →SoftwareontwikkelingPython-generatoren en opbrengst uitgelegd: bespaar geheugen met echte voorbeelden
Python-generatoren en opbrengst uitgelegd met code die we hebben uitgevoerd: hoe ze pauzeren en hervatten, gemeten geheugenbesparingen, pijplijnen, …
Lees artikel →Kunstmatige intelligentieWat zijn inbeddingen? Vectoren en cosinusovereenkomst eenvoudig uitgelegd
Wat zijn insluitingen? Een gids in duidelijke taal over vectoren, cosinusovereenkomst en semantisch zoeken, met een klein voorbeeld dat we hebben ui…
Lees artikel →DevOpsImplementatie van blauwgroen versus kanarie: verschillen, afwegingen en hoe te kiezen
Blauw-groen versus kanarie-implementatie uitgelegd: hoe elk werkt, kosten, risico's en terugdraaien, stabiele gebruikersbucket die we hebben getest,…
Lees artikel →TestautomatiseringSchilferige tests: wat de oorzaak ervan is en hoe u ze voorgoed kunt oplossen
Zwakke tests uitgelegd met demo's die we hebben uitgevoerd: de belangrijkste oorzaken, hoe je er een kunt bevestigen en diagnosticeren, goede oploss…
Lees artikel →SoftwaretestenBelastingtesten versus stresstesten: verschillen, statistieken en k6-voorbeelden
Belastingtesten versus stresstesten uitgelegd: wat elk bewijst, piek- en soak-tests, de statistieken die er toe doen, waarom gemiddelden misleiden, …
Lees artikel →SoftwaretestenTestcase versus testscenario: belangrijkste verschillen, voorbeelden en wanneer deze te gebruiken
Testcase versus testscenario uitgelegd: duidelijke definities, een tabel naast elkaar, één functie voor het opnieuw instellen van wachtwoorden, over…
Lees artikel →Databases & SQLOptimalisatie van SQL-query's: lees uitvoeringsplannen en repareer langzame query's, met timing
Optimalisatie van SQL-query's getest op een miljoen rijen: lees uitvoeringsplannen, voeg de juiste indexen toe, repareer niet-sargeerbare filters, g…
Lees artikel →Databases & SQLSQL UNION versus UNION ALL: verschillen, voorbeelden en geteste prestaties
SQL UNION versus UNION ALL uitgelegd met de query's die we hebben uitgevoerd: duplicaten, de regels volgen beide, een echte databug, gemeten prestat…
Lees artikel →SoftwareontwikkelingJavaScript Fetch API-tutorial: GET, POST, foutafhandeling en time-outs
Een JavaScript Fetch API-tutorial getest tegen een lokale server: GET en POST, waarom 404 niet gooit, een herbruikbare helper, queryreeksen, time-ou…
Lees artikel →SoftwareontwikkelingPython-bestandsverwerking: lezen, schrijven, CSV, JSON en pathlib met voorbeelden
Het verwerken van Python-bestanden uitgelegd met code die we hebben uitgevoerd: open modi, met het lezen van grote bestanden, CSV, JSON, pathlib, bi…
Lees artikel →SoftwareontwikkelingPython-klassen en -objecten: OOP-zelfstudie met echte voorbeelden
Python-klassen en -objecten uitgelegd met code die we hebben uitgevoerd: __init__, methoden, klasse versus instantie-attributen, overerving, eigensc…
Lees artikel →Kunstmatige intelligentieWat is een groot taalmodel? Hoe LLM's werken, uitgelegd zonder de hype
Wat is een groot taalmodel? LLM's uitgelegd in duidelijke taal: tokens, inbedding, aandacht, training, temperatuur- en contextvensters, een kleine d…
Lees artikel →DevOpsLinux-opdrachten voor beginners: de essentiële terminalgids met voorbeelden
Linux-opdrachten voor beginners, gegroepeerd op taak: navigatie, bestanden, logboeken bekijken, grep, pipelines, machtigingen, processen en archieve…
Lees artikel →TestautomatiseringSelenium versus toneelschrijver: belangrijkste verschillen, code vergeleken en welke te kiezen
Selenium versus Playwright vergeleken: architectuur, wachten, browser- en taalondersteuning, debuggen, dezelfde test in beide tools, migratieadvies …
Lees artikel →SoftwareontwikkelingGit merge vs rebase: verschillen, voorbeelden en wanneer je wat gebruikt
Git merge vs rebase uitgelegd met echte logoutput, een vergelijkingstabel, de gouden regel van rebase en een veilige teamwerkwijze.
Lees artikel →SoftwareontwikkelingPython list comprehension: syntaxis, voorbeelden en wanneer je het vermijdt
Python list comprehension uitgelegd: syntaxis, filteren, if-else, geneste lussen, gemeten tijden en wanneer een gewone for-lus beter is.
Lees artikel →Databases & SQLSQL JOIN uitgelegd met voorbeelden (Inner, Left, Right, Full)
Begrijp SQL joins stap voor stap met voorbeeldtabellen en queries: INNER, LEFT, RIGHT, FULL, CROSS en SELF, plus veelgemaakte fouten en prestatietips.
Lees artikel →