Asynchrone JavaScript
await. Handel fouten af met catch, try en finally en voer onafhankelijke taken tegelijk uit met Promise.all en de andere promisecombinatoren.BeginnerLes 7 van 9 in Cursus JavaScript programmeren: DOM, events, asynchrone code en modules
Wat je leert
- Onderscheid synchrone en asynchrone code, volg de eventloop via de aanroepstapel en de wachtrijen en plan werk met timers.
- Onderscheid synchrone en asynchrone codeLeg uit waarom JavaScript steeds één deel van je code uitvoert, waarom langdurige code een pagina laat vastlopen en hoe een programma dankzij asynchrone bewerkingen kan blijven werken terwijl het wacht.
- Volg de eventloopVolg hoe de aanroepstapel, de taakwachtrij en de microtaakwachtrij de uitvoeringsvolgorde van code bepalen en voorspel de uitvoer van code die timers en promises combineert.
- Plan de uitvoering van code met timersVoer code met setTimeout en setInterval één keer na een vertraging of herhaaldelijk uit, annuleer timers en leg uit waarom de vertraging van een timer een minimale wachttijd is en geen exact tijdstip.
- Onderscheid synchrone en asynchrone code
- Geef functies door aan asynchrone bewerkingen, meld fouten volgens de conventie met de fout als eerste argument en herken waarom diep geneste callbacks moeilijk te lezen en te onderhouden zijn.
- Geef callbacks door aan asynchrone bewerkingenSchrijf functies die een callback accepteren, onderscheid synchrone en asynchrone callbacks en geef een asynchroon resultaat door aan de code die het nodig heeft.
- Handel fouten in callbacks af en voorkom callbackhelGebruik de conventie met de fout als eerste argument, leg uit waarom try en catch een fout die later in een callback wordt opgeworpen niet kunnen opvangen en maak diep geneste callbackcode vlakker.
- Geef callbacks door aan asynchrone bewerkingen
- Maak promises, reageer op hun resultaten met then, catch en finally en schakel asynchrone stappen aaneen tot een leesbare reeks.
- Leg de toestanden van promises uit en maak een promiseBeschrijf de toestanden waarin een promise kan verkeren, maak promises met de Promise-constructor en verpak een timer in een promise.
- Reageer op promises met then, catch en finallyRegistreer handlers die worden uitgevoerd wanneer een promise succesvol is voltooid, afgewezen of afgehandeld en gebruik ze om een pagina op het juiste moment bij te werken.
- Schakel promises aaneen tot een reeksGeef waarden en promises terug vanuit handlers om vlakke ketens van afhankelijke stappen te bouwen en voorkom fouten door ontbrekende returns en nesting.
- Leg de toestanden van promises uit en maak een promise
- Schrijf asynchrone functies, pauzeer ze met await totdat promises zijn afgehandeld, herschrijf promiseketens en callback-API's in deze vorm en voorkom veelgemaakte fouten in asynchrone code.
- Schrijf asynchrone functies en gebruik awaitDeclareer asynchrone functies, leg uit wat ze teruggeven en gebruik await om een functie te pauzeren totdat een promise is afgehandeld zonder de pagina te blokkeren.
- Herschrijf promiseketens en callback-API's met async en awaitZet promiseketens om in asynchrone functies, verpak functies met callbacks in promises zodat je erop kunt wachten met await en kies een heldere stijl.
- Voorkom veelgemaakte fouten met async en awaitHerken en herstel vergeten awaits, asynchrone callbacks in forEach, promises die als voorwaarden worden gebruikt en asynchrone aanroepen waarvan fouten niet worden afgehandeld.
- Schrijf asynchrone functies en gebruik await
- Vang fouten op en herstel ervan in promiseketens en met try en catch, ruim op met finally en zorg dat fouten nooit onopgemerkt blijven.
- Handel afwijzingen in promiseketens afVolg hoe een afwijzing door een promiseketen gaat, herstel ervan of geef die door en plaats catch-handlers zo dat ze de juiste fouten afhandelen.
- Gebruik try, catch en finally met awaitGebruik Error-objecten en de try-, catch- en finally-instructie om fouten van promises waarop met await wordt gewacht af te handelen, betrouwbaar op te ruimen en nuttige terugvalopties te bieden.
- Houd fouten zichtbaar en zorg voor passend herstelVoorkom onafgehandelde afwijzingen en verzwegen fouten, voeg context toe aan fouten en kies tussen een nieuwe poging, een terugvaloptie en de gebruiker informeren.
- Handel afwijzingen in promiseketens af
- Start onafhankelijke taken tegelijk, wacht er samen op en kies de promisecombinator die past bij alles-of-niets, zoveel mogelijk resultaten, de eerste voltooide taak of het eerste succes.
- Start taken tegelijk en wacht met Promise.allVoer onafhankelijke asynchrone taken tegelijk uit, combineer hun resultaten met Promise.all en leg uit hoe de timing en foutafhandeling werken.
- Kies tussen Promise.allSettled, Promise.race en Promise.anyGebruik allSettled wanneer elke uitkomst telt, race voor de eerste taak die wordt afgehandeld, bijvoorbeeld bij een tijdslimiet, en any voor het eerste succes, en vergelijk alle vier de combinatoren.
- Start taken tegelijk en wacht met Promise.all
Asynchrone JavaScript: uitleg
1Leg uit hoe JavaScript asynchrone code uitvoert
JavaScript moet vaak wachten terwijl iemand klikt of een timer aftelt. Tijdens dat wachten moet de pagina blijven reageren. Vorm een beeld van wat nu wordt uitgevoerd, wat in de omgeving wacht en wat voor later in een wachtrij komt. Gebruik dat beeld vervolgens om console-uitvoer te volgen en timers in te plannen. De sleutel is bij te houden wanneer een functie wordt aangeroepen, niet alleen waar haar naam in de code staat.
1.1 Onderscheid synchrone en asynchrone code
Synchrone code voltooit de huidige instructie voordat de volgende aan bod komt. De JavaScript van je pagina draait op één hoofdthread, dus twee delen van die code worden niet op hetzelfde moment uitgevoerd. Dit is eenvoudig te volgen totdat een instructie werk start waarvan het resultaat later binnenkomt.
Nu starten, het resultaat later verwerken
Een asynchrone bewerking begint nu en levert het resultaat achteraf via een callback of een promise. De browser of Node.js is de uitvoeringsomgeving. Die beheert het wachten terwijl je programma doorgaat. Timers, gebruikersinvoer en het laden van afbeeldingen werken allemaal op deze manier.
console.log("Opening");
setTimeout(() => console.log("Ready"), 700);
console.log("Waiting");
Lees de aanroep van setTimeout als “registreer deze functie”. De aanroep keert terug zonder 700 ms te wachten. Het huidige script logt daarom eerst “Opening” en “Waiting”. Na ongeveer 700 ms kan de callback worden uitgevoerd en “Ready” loggen. De exacte vertraging hangt ook af van wanneer de hoofdthread vrijkomt.
Hier zijn twee verschillende activiteiten: de omgeving houdt de timer bij en JavaScript roept uiteindelijk de functie aan die het resultaat logt. Het wachten overlapt ander werk; de callback draait nog steeds op de hoofdthread.
Waarom een pagina vastloopt
Een lange synchrone lus houdt de thread bezet. Zolang die draait, kunnen klikhandlers en timercallbacks niet worden uitgevoerd en kan de pagina haar weergave niet via die thread bijwerken. Een timer zonder vertraging registreren geeft die geen toestemming om de lus te onderbreken.
Stel dat een timer klaar is terwijl een berekening nog loopt. De omgeving zet de callback in een wachtrij. Pas als de berekening klaar is, komt de thread vrij; de verstreken tijd van de timer alleen kan daarvoor niet zorgen. Een callback die een andere lange berekening uitvoert, kan de pagina ook laten vastlopen.
Om de invloed van een berekening op de responsiviteit te beperken, deel je die op in kleinere stukken waartussen andere taken kunnen worden uitgevoerd, of verplaats je geschikt werk naar een webworker, een aparte achtergrondthread die de browser biedt. Als je een grote lus alleen maar in een asynchrone functie zet, blijft die lus op de hoofdthread draaien.
Drie manieren om een resultaat te ontvangen
Bij een callback geef je de volgende actie als functie door. Een promise vertegenwoordigt één uiteindelijke uitkomst en laat je handlers voor succes en fouten koppelen. Een asynchrone functie werkt met promises, terwijl await de eigen uitvoering op een duidelijk punt kan pauzeren.
Deze vormen bestaan naast elkaar. Een timer accepteert nog steeds een callback, ook wanneer de omringende code promises gebruikt. Een asynchrone functie kan met await wachten op een promise die door een functie rond een callback is gemaakt. Kies de vorm waarbij het pad van het resultaat makkelijk te volgen is.
Markeer bij het lezen van asynchrone code eerst de instructies die tijdens de huidige aanroep worden uitgevoerd. Markeer daarna de functies die voor later worden geregistreerd. Een regel binnen een geregistreerde callback wordt niet uitgevoerd alleen omdat de uitvoering de regel heeft bereikt die die functie maakt.
Belangrijkste punten
- Synchrone JavaScript houdt de hoofdthread bezet totdat het huidige werk klaar is.
- De uitvoeringsomgeving beheert het wachten en plant code in die het resultaat verwerkt.
- Asynchrone callbacks worden later op de hoofdthread uitgevoerd.
- Een asynchrone functie kan de pagina nog steeds blokkeren met een lange berekening.
1.2 Volg de eventloop
De eventloop coördineert werk dat klaar is om te worden uitgevoerd. Om uitvoer te voorspellen, houd je drie dingen apart: de functies die nu draaien, callbacks die als taken wachten en werk dat samenhangt met promises en als microtaak wacht.
Volg de stapel en de wachtrijen
De aanroepstapel bevat actieve functieaanroepen. Als je een functie aanroept, komt er een frame op de stapel; als de functie terugkeert, wordt dat frame verwijderd. De huidige functie draait totdat die terugkeert of een andere functie aanroept.
Een taak kan een script, een timercallback of een klikhandler zijn. Taken worden één voor één uitgevoerd. Browsers kunnen meerdere taakwachtrijen hebben, dus ga er niet van uit dat al het werk uit alle bronnen in één universele wachtrij komt waarin het oudste werk eerst wordt uitgevoerd.
De microtaakwachtrij bevat promisehandlers van then, catch en finally, vervolgcode na await en callbacks die aan queueMicrotask zijn doorgegeven. Bij een controlepunt voor microtaken na het huidige werk wordt de wachtrij leeggemaakt voordat de volgende taak begint. Microtaken die tijdens het leegmaken worden toegevoegd, worden ook vóór de volgende taak uitgevoerd.
Volg één script
console.log("begin");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("finish");
- Het script logt “begin”.
- Het registreert een timercallback; het roept die niet aan.
- Het zet de handler van de succesvol voltooide promise als microtaak in de wachtrij.
- Het logt “finish” en voltooit het huidige werk.
- De microtaak logt “promise”.
- De timertaak logt “timer”.
Een al succesvol voltooide promise plant de handler nog steeds voor later in. Een vertraging van 0 ms plant nog steeds een timertaak in.
Neem werk mee dat onderweg wordt toegevoegd
Promise.resolve()
.then(() => console.log("first"))
.then(() => console.log("third"));
queueMicrotask(() => console.log("second"));
console.log("now");
Het script logt “now”. De microtaakwachtrij bevat aanvankelijk eerst de eerste promisehandler en daarna de queueMicrotask-callback. Wanneer de eerste handler “first” logt en terugkeert, komt de volgende promisehandler klaar te staan. Die nieuwe handler komt achteraan in de wachtrij. De resterende uitvoer is “second” en daarna “third”.
Voltooiing en responsiviteit
Zodra een taak of microtaak begint, kan asynchroon werk in de wachtrij die niet onderbreken. Dankzij deze ononderbroken uitvoering tot voltooiing kun je over een lokale wijziging nadenken zonder dat een timer plotseling halverwege de functie ingrijpt.
Nadat de microtaken klaar zijn, kan de browser het scherm bijwerken wanneer er gelegenheid is om te renderen. De browser hoeft niet na elke taak opnieuw te tekenen. Een eindeloze stroom microtaken kan zowel latere taken als het renderen verhinderen: elke nieuwe microtaak zorgt dat het huidige leegmaken doorgaat. Werk uitstellen naar een microtaak garandeert dus niet dat de pagina de kans krijgt om te reageren.
Schrijf bij het volgen van de uitvoering eerst de directe uitvoer op, werk de microtaken op volgorde af en ga pas daarna naar de klaarstaande timertaken. Voeg nieuw ingepland werk aan de juiste wachtrij toe op het moment dat de code het daadwerkelijk inplant.
Volg een timer die door een microtaak wordt gemaakt
setTimeout(() => console.log("outer timer"), 0);
Promise.resolve().then(() => {
console.log("job one");
setTimeout(() => console.log("inner timer"), 0);
}).then(() => console.log("job two"));
console.log("script");
Het script registreert de buitenste timer en de eerste promisehandler en logt dan “script”. De eerste microtaak logt “job one” en registreert de binnenste timer. Wanneer die handler terugkeert, komt “job two” in de wachtrij en wordt ook vóór een timertaak uitgevoerd. De uitvoer eindigt met “outer timer” en daarna “inner timer”: beide timers hebben dezelfde vertraging en de buitenste is als eerste geregistreerd.
Een timer die tijdens een microtaak wordt gemaakt, maakt van de callback geen microtaak. Deel het werk in op basis van de API waaraan het voor inplanning wordt doorgegeven en voeg het dan toe aan de juiste wachtrij.
Belangrijkste punten
- De aanroepstapel houdt actieve aanroepen bij.
- Promisehandlers en vervolgcode na await worden als microtaken uitgevoerd.
- Werk nieuw toegevoegde microtaken af voordat je een volgende taak neemt.
- Callbacks in de wachtrij kunnen JavaScript die op dat moment draait niet onderbreken.
- Renderen is mogelijk na de microtaken, maar niet gegarandeerd na elke taak.
1.3 Plan de uitvoering van code met timers
Timers plannen callbacks in voor later. Ze meten een vertraging in milliseconden: 1000 ms is 1 s. De vertraging bepaalt vanaf wanneer een callback kan worden uitgevoerd; door ander werk en beperkingen van de browser kan dat later gebeuren.
Plan en annuleer één aanroep
function remind(topic) {
console.log("Review " + topic);
}
const reminder = setTimeout(remind, 2000, "promises");
// To cancel before it runs:
clearTimeout(reminder);
Extra argumenten na de vertraging worden doorgegeven aan de callback. Een ontbrekende vertraging geldt als 0. Bewaar de teruggegeven timerreferentie als je de timer mogelijk moet annuleren. Browsers geven een numerieke ID terug; Node.js geeft een Timeout-object terug. Beide werken met clearTimeout. Een voltooide of niet-bestaande timer annuleren heeft geen effect.
Geef een functie door: setTimeout(remind, 2000, "promises"). Als je setTimeout(remind("promises"), 2000) schrijft, wordt remind direct aangeroepen en wordt de retourwaarde aan de timer doorgegeven. Een pijlfunctie is een andere manier om een aanroep met argumenten uit te stellen: setTimeout(() => remind("promises"), 2000).
Herhaal met een interval of een nieuwe eenmalige timer
let ticks = 0;
const clock = setInterval(() => {
ticks++;
console.log("Tick " + ticks);
if (ticks === 3) clearInterval(clock);
}, 1000);
De eerste tik is na ongeveer één seconde. De derde callback stopt toekomstige tikken. Bewaar de referentie van de herhalende timer en gebruik clearInterval wanneer het herhaalde werk moet eindigen.
Een callback kan in plaats daarvan zelf de volgende eenmalige timer inplannen. Als de callback setTimeout aan het einde van het synchrone werk aanroept, begint de volgende vertraging na dat werk. Dit geeft een pauze tussen uitvoeringen en maakt elke keer een andere vertraging mogelijk. Een herhalende timer blijft uitvoeringen volgens een vast schema aanvragen; langzaam werk kan weinig pauze overlaten. Als een uitvoering asynchroon werk start, wacht dan ook op dat werk voordat je de volgende uitvoering inplant wanneer overlap een probleem zou zijn.
Timing heeft grenzen
Een bezette thread vertraagt callbacks die klaarstaan. Browsers vertragen timers ook in tabbladen op de achtergrond, vaak tot ongeveer één controle per seconde of minder. Wanneer het nestingsniveau van timers groter is dan vijf, verhoogt de HTML-timerregel vertragingen onder 4 ms tot 4 ms. Ook bij zeer grote vertragingen is voorzichtigheid nodig: de gebruikelijke grens voor een 32-bits getal met teken is 2147483647 ms, ongeveer 24,8 dagen; grotere waarden kunnen overlopen en veel eerder afgaan.
Leg de juiste luswaarde vast
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 100);
}
Elke let-iteratie heeft een eigen binding, dus de uitvoer is “1”, “2”, “3”. Met var delen alle callbacks dezelfde variabele. Na de lus is die 4, dus elke callback logt “4”.
Wacht op een rustperiode
Uitstellen tot na een rustperiode annuleert een wachtende eenmalige timer telkens wanneer een nieuw event binnenkomt. Een nieuwe eenmalige timer plant de actie vervolgens na het laatste event. Bij een vertraging van 300 ms blijft typen om de 100 ms de actie uitstellen; die wordt ongeveer 300 ms nadat het typen stopt uitgevoerd. Bewaar de referentie buiten de handler, zodat de volgende aanroep de vorige timer kan annuleren.
Bouw een handler die wacht op een rustperiode
let pendingSave;
function scheduleSave(text) {
clearTimeout(pendingSave);
pendingSave = setTimeout(() => {
console.log("Saved: " + text);
}, 300);
}
scheduleSave("n");
scheduleSave("no");
scheduleSave("note");
Deze aanroepen vinden plaats in hetzelfde script. Elke aanroep annuleert de eerdere timerreferentie voordat die wordt vervangen, dus alleen “Saved: note” wordt gelogd, na ongeveer 300 ms. De laatste callback legt de tekstparameter van die aanroep vast. De referentie staat buiten de functie, zodat elke aanroep de eerder ingeplande eenmalige timer kan annuleren.
Als de referentie bij elke aanroep opnieuw binnen de functie werd gedeclareerd, zou de volgende aanroep de referentie van de vorige timer niet meer hebben. Als een handler zonder annuleren een timer zou inplannen, zou die alle drie de berichten leveren. Voor uitstellen tot na een rustperiode zijn zowel de gedeelde referentie als het annuleren vóór vervanging nodig.
Belangrijkste punten
- Geef een callbackfunctie door en meet vertragingen in milliseconden.
- Gebruik de teruggegeven referentie om een timer te annuleren.
- Timervertragingen garanderen geen exacte uitvoeringstijden.
- Een herhalende timer blijft lopen totdat die wordt geannuleerd.
- Gebruik let voor afzonderlijke bindingen per lusiteratie.
- Bij uitstellen tot na een rustperiode begint de vertraging na elk event opnieuw.
2Schrijf en beheer code met callbacks
Met een callback kan een aanroeper het volgende werk als functie aanleveren. Dit eenvoudige idee ligt ten grondslag aan timers, events en veel oudere API's. Hier schrijf je een kleine bewerking met een callback, volg je het resultaat naar de code die het nodig heeft en geef je fouten door via een foutparameter. Je ziet ook hoe afhankelijke stappen geneste code maken en waarom consistente foutafhandeling op elk niveau belangrijk is.
2.1 Geef callbacks door aan asynchrone bewerkingen
Een callback is een functie die je aan een andere functie doorgeeft om door die functie te worden aangeroepen. De code van de callback beschrijft wat er met een resultaat moet gebeuren. Of de callback tijdens de huidige aanroep of later wordt uitgevoerd, hangt af van de API die de callback ontvangt.
Een callback kan synchroon worden uitgevoerd
const doubled = [2, 5].map(n => n * 2);
console.log(doubled);
map roept de callback voor elk element aan voordat de nieuwe array wordt teruggegeven. Het resultaat is [4, 10]. De pijlfunctie is een callback, maar deze code plant geen toekomstig werk in. Je kunt doubled op de volgende regel gebruiken omdat de array al compleet is.
Lever een vertraagd resultaat
function prepareLabel(name, callback) {
setTimeout(() => {
callback("Label: " + name);
}, 200);
}
prepareLabel("Mira", label => {
console.log(label);
});
console.log("Label requested");
prepareLabel start een timer en keert terug. Het script logt “Label requested”. Na ongeveer 200 ms stelt de timercallback het label samen en roept de meegegeven callback aan, die “Label: Mira” logt. De bewerking bepaalt wanneer de callback wordt aangeroepen; de aanroeper bepaalt wat er met het resultaat gebeurt.
Zet afhankelijk werk waar het resultaat bestaat
let label;
prepareLabel("Mira", value => {
label = value;
});
console.log(label);
Dit logt undefined: de toewijzing heeft nog niet plaatsgevonden. Verplaats console.log(value) naar de callback. Als je een langere, niet-gerelateerde timer zou toevoegen voordat je label leest, zou je alleen maar gokken wanneer het resultaat beschikbaar is; met de resultaatcallback koppel je het werk aan de daadwerkelijke voltooiing.
Een return binnen een vertraagde callback kan de waarde ook niet vanuit de buitenste functie teruggeven. De buitenste aanroep is al voorbij. In het voorbeeld zou return "Label: Mira" binnen de timercallback terugkeren naar het timermechanisme, waardoor de aanroeper van prepareLabel het vertraagde label niet ontvangt.
Geef de volgende actie door zonder die aan te roepen
Een benoemde callback werkt ook:
function showLabel(label) {
console.log(label);
}
prepareLabel("Mira", showLabel);
showLabel is de functiewaarde. showLabel() zou de functie direct zonder label aanroepen en in plaats daarvan de retourwaarde doorgeven. Gebruik een omhullende pijlfunctie wanneer de latere actie een bepaald argument of meerdere instructies nodig heeft.
Voor twee afhankelijke asynchrone stappen start je de tweede binnen de callback van de eerste stap. Geef bijvoorbeeld na ontvangst van een gebruiker die gebruiker door aan een bewerking die de instellingen voorbereidt. Als je beide buiten hun callbacks zou starten, zou die afhankelijkheid niet worden gelegd.
De ontvangende functie bepaalt hoe vaak ze de callback aanroept. Sommige callbacks, zoals eventhandlers, worden bewust vaak aangeroepen. Een bewerking met één resultaat moet de resultaatcallback één keer aanroepen. Een promise geeft zo'n bewerking een resultaat dat maar één keer kan worden afgehandeld.
Belangrijkste punten
- De timing van een callback wordt bepaald door de API die de callback aanroept.
- map gebruikt synchrone callbacks; een timercallback wordt later uitgevoerd.
- Gebruik een vertraagd resultaat binnen de callback of in code die daaruit wordt aangeroepen.
- Terugkeren uit een callback betekent niet dat de buitenste functie terugkeert.
2.2 Handel fouten in callbacks af en voorkom callbackhel
Een asynchrone bewerking kan zowel mislukken als slagen. Een callback-API heeft een duidelijke manier nodig om elke uitkomst te melden en een duidelijke plaats waar de aanroeper die kan afhandelen.
Controleer eerst de fout
De conventie met de fout als eerste argument gebruikt callback(err, result). Een geslaagde aanroep geeft null door voor de fout. Een mislukte aanroep geeft een Error-object door en levert geen bruikbaar resultaat.
function readLabel(id, callback) {
setTimeout(() => {
if (id <= 0) {
callback(new Error("Invalid label id"));
return;
}
callback(null, { id: id, text: "Draft" });
}, 200);
}
readLabel(4, (err, label) => {
if (err) {
console.log(err.message);
return;
}
console.log(label.text);
});
De producer stopt na het melden van een ongeldige ID en kan dus niet ook succes melden. De consument stopt na het vaststellen van een fout en kan dus niet proberen label.text uit een ontbrekend resultaat te lezen. Met ID 4 is de uitvoer “Draft”. Met ID 0 is die “Invalid label id”.
Beide returns zijn belangrijk. Zonder de return van de consument zou een tweede fout over een ongedefinieerde waarde de nuttige oorspronkelijke melding kunnen verbergen. Zonder de return van de producer zou één bewerking de callback twee keer kunnen aanroepen. Voor een API met één resultaat geldt: meld precies één uitkomst.
Een omringende try is al klaar
try {
setTimeout(() => {
throw new Error("Late failure");
}, 0);
} catch (err) {
console.log("Handled");
}
De try plant de timer in en is klaar. De callback werpt in een latere taak een fout op, wanneer die try niet meer actief is, dus “Handled” wordt niet gelogd. Vang een mogelijke fout op binnen de callback of geef een fout door via de foutparameter van de API. Een latere functie die de callback in een promise verpakt, moet die callbackfout expliciet omzetten in een afwijzing.
Houd afhankelijke stappen leesbaar
Callbackcode wordt moeilijk te volgen wanneer elke volgende stap binnen de callback van de vorige stap staat. Een profiel lezen, dan de projecten ervan en vervolgens de taken van een project, levert meerdere nestingsniveaus op. Elke bewerking heeft ook een eigen foutcontrole nodig. Dit patroon heet vaak callbackhel of de onheilspiramide.
Benoemde functies kunnen die stappen betekenisvolle namen geven en de zichtbare nesting verminderen. Houd de foutmelding ertussen consistent: een latere stap kan net zo goed mislukken als de eerste. Promises en asynchrone functies bieden een vlakkere manier om dezelfde reeks uit te drukken en tegelijk de doorgifte van fouten te behouden.
Niet alle callback-API's gebruiken een foutparameter. Sommige browserobjecten hebben aparte events voor succes en fouten. Stel voor een afbeelding de handlers onload en onerror in voordat je src toewijst, zodat beide uitkomsten een handler hebben wanneer het laden begint. Lees het contract van de API voordat je aanneemt dat een parameter met de naam err bestaat.
Belangrijkste punten
- Een callback volgens de conventie met de fout als eerste argument ontvangt err vóór het resultaat.
- Keer terug na het melden of afhandelen van een fout.
- Bewerkingen met één resultaat moeten hun callback precies één keer aanroepen.
- Een buitenste try kan een fout die een latere callback opwerpt niet opvangen.
- Benoemde stappen of code met promises verminderen diepe nesting.
3Maak en gebruik promises
Een promise geeft een bewerking één gedeeld object voor het uiteindelijke resultaat. Het maakt succes en fouten expliciet en laat je afhankelijke stappen verbinden zonder elke stap in de vorige te nesten. Begin met het bouwen van een promise rond een timer. Koppel vervolgens handlers, volg de nieuwe promises die ze teruggeven en controleer of elke asynchrone stap verbonden blijft met de keten die ervan afhankelijk is.
3.1 Leg de toestanden van promises uit en maak een promise
Een promise vertegenwoordigt een uiteindelijke uitkomst. Een functie kan die direct teruggeven, zodat de aanroeper werk kan verbinden aan een resultaat dat nog niet bestaat.
Toestanden en afhandeling
Een promise is in afwachting totdat die succesvol wordt voltooid met een waarde of wordt afgewezen met een reden. Succesvol voltooide en afgewezen promises zijn afgehandeld; hun uitkomst kan niet veranderen.
“Vastgelegd” is preciezer dan een alledaags synoniem voor “succesvol”. Een promise waarvan de uitkomst is vastgelegd met een andere promise die nog in afwachting is, volgt de uiteindelijke uitkomst van die promise, ook bij afwijzing. De uitkomst kan dus zijn vastgelegd terwijl de promise nog in afwachting is.
Maak een promise rond een bewerking
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
wait(200).then(() => console.log("Pause finished"));
new Promise roept de executorfunctie direct aan. Die executorfunctie registreert een timer. Wanneer de timer resolve aanroept, wordt de promise succesvol voltooid met undefined en wordt de geregistreerde then-handler later als microtaak uitgevoerd. Deze hulpfunctie biedt een herbruikbare pauze zonder de thread te blokkeren.
Een executorfunctie kan zowel resolve als reject ontvangen:
function reserve(quantity) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (quantity <= 8) resolve(8 - quantity);
else reject(new Error("Not enough stock"));
}, 200);
});
}
reserve(3).then(left => console.log(left));
reserve(9).catch(err => console.log(err.message));
Voor deze positieve aantallen wordt de eerste aanroep succesvol voltooid met 5 en wordt de tweede afgewezen met “Not enough stock”. Dankzij de promise kan de aanroeper beide uitkomsten via hetzelfde teruggegeven object ontvangen.
Het maken start de executorfunctie
console.log("before");
const result = new Promise(resolve => {
console.log("creating");
resolve(12);
});
console.log("after");
result.then(value => console.log(value));
De uitvoer is “before”, “creating”, “after”, “12”. Het koppelen van then start deze executorfunctie niet. De constructor heeft die al uitgevoerd; then registreert code om de uitkomst te gebruiken.
Alleen de eerste aanroep van resolve of reject bepaalt het lot van de promise. Latere aanroepen kunnen dat niet veranderen. Dit geldt ook wanneer bij de eerste vastlegging de uitkomst wordt overgenomen van een andere promise waarvan het resultaat nog in afwachting is. Het aanroepen van resolve stopt de gewone code van de executorfunctie niet, dus gebruik return wanneer een tak moet eindigen.
Laat elk uitvoeringspad correct eindigen
Een synchrone fout in de executorfunctie wijst de promise automatisch af. Een fout in een latere timercallback valt buiten die aanroep en wordt niet omgezet in een afwijzing. Gebruik daar reject(new Error("...")); anders kan de fout niet worden opgevangen terwijl de promise in afwachting blijft.
Een tak die geen van beide functies aanroept, kan de aanroeper voor altijd laten wachten. Gebruik Error-objecten als reden voor afwijzing, zodat handlers een bericht kunnen lezen en ontwikkelaarstools nuttige foutinformatie kunnen tonen.
Promise.resolve(value) maakt een succesvol voltooide promise voor een gewone waarde of neemt de uitkomst van een andere promise over. Promise.reject(error) maakt een afgewezen promise. Koppel bij gebruik van die laatste een afhandeling voor de afwijzing.
Volg een overgenomen uitkomst
const inner = new Promise(resolve => {
setTimeout(() => resolve("packed"), 250);
});
const outer = new Promise((resolve, reject) => {
resolve(inner);
reject(new Error("Too late to replace it"));
});
outer.then(value => console.log(value));
De binnenste executorfunctie start de timer tijdens het maken. De buitenste executorfunctie legt vervolgens de uitkomst vast met inner, waardoor outer de uiteindelijke uitkomst van inner zal volgen. De latere aanroep van reject kan die beslissing niet vervangen. Outer blijft in afwachting totdat inner succesvol wordt voltooid en de handler logt na ongeveer 250 ms “packed”.
Als inner in plaats daarvan zou worden afgewezen, zou outer die afwijzing volgen. De eerste aanroep van resolve koos welke uitkomst gevolgd moest worden; die garandeerde geen succes. Dit onderscheid is belangrijk wanneer een omhullende functie een andere promise doorgeeft in plaats van een voltooide gewone waarde.
Belangrijkste punten
- Promises in afwachting worden succesvol voltooid of afgewezen; afgehandelde uitkomsten blijven vaststaan.
- Bij het vastleggen van de uitkomst kan de uiteindelijke uitkomst van een andere promise worden overgenomen.
- De executorfunctie wordt synchroon uitgevoerd tijdens het maken.
- Gebruik resolve en reject om latere callbacks aan de promise te verbinden.
- Een fout die een latere callback opwerpt, wijst de omringende promise niet automatisch af.
3.2 Reageer op promises met then, catch en finally
Promisehandlers verbinden code aan succes, een fout of beide uitkomsten. Elke handlerregistratie geeft ook een nieuwe promise terug, zodat het volgende stuk code kan voortbouwen op wat die handler oplevert.
Gebruik de handler die bij de uitkomst past
p.then(onFulfilled) ontvangt een waarde bij succesvolle voltooiing. p.catch(onRejected) ontvangt een reden voor afwijzing; het is een verkorte schrijfwijze voor p.then(undefined, onRejected). Handlers zijn functies en worden als microtaken uitgevoerd, ook als p al is afgehandeld.
Promise.resolve("draft")
.then(text => console.log(text));
console.log("registered");
Het script logt “registered” voordat de handler “draft” logt. Een direct beschikbare waarde maakt de handler niet synchroon.
Een functie doorgeven is essentieel. then(console.log("draft")) logt “draft” tijdens de registratie en geeft undefined door als handler. Gebruik then(() => console.log("draft")) wanneer het bericht later moet worden gelogd. then(console.log) geeft de logfunctie zelf door, zodat die de waarde ontvangt.
Ruim op na succes of een fout
finally is nuttig om een laadindicator te verbergen of een knop weer beschikbaar te maken nadat een bewerking is afgehandeld. De callback ontvangt geen waarde of fout als argument.
Promise.resolve(4)
.finally(() => console.log("Cleanup"))
.then(value => console.log(value));
De uitvoer is “Cleanup” en daarna “4”. Als de opruimcallback een gewone waarde teruggeeft, vervangt die de 4 niet. Een teruggegeven promise kan de volgende stap uitstellen totdat het opruimen klaar is. Als het opruimen een fout opwerpt of een afgewezen promise teruggeeft, wordt die fout de nieuwe uitkomst van de keten.
Een gebruikelijke gang van zaken op een pagina is: stel vóór de start een laadstatus in, verwerk een resultaat in then, meld een fout in catch en herstel de laadstatus in finally. Zet het werk dat van het resultaat afhangt in then of catch, waar het benodigde argument beschikbaar is.
Onderscheid afzonderlijke registraties van een keten
const p = Promise.resolve(4);
p.then(value => value * 2);
p.then(value => console.log(value));
Dit zijn twee afzonderlijke callbackregistraties bij p. Beide ontvangen 4. De vermenigvuldiging hoort bij de nieuwe promise die de eerste then teruggeeft; ze verandert p niet en geeft geen waarde door aan de tweede registratie.
Verbind ze om de getransformeerde waarde door te geven:
p.then(value => value * 2)
.then(value => console.log(value));
Nu ontvangt de laatste handler 8. Het verschil is aan welke promise elke handler is gekoppeld.
Het teruggegeven object is nog steeds een promise
In const next = p.then(value => value * 2) is next een nieuwe promise, niet het getal 8. De handler kan worden uitgevoerd nadat het omringende script is geëindigd en kan zelf vertraagd werk teruggeven. Gebruik een andere handler of await om de waarde te krijgen. Als je next direct als getal behandelt, verlies je deze informatie over de timing.
Belangrijkste punten
- Gebruik then voor waarden, catch voor afwijzingen en finally om op te ruimen.
- Geef functies door als handlers.
- Elke then geeft een nieuwe promise terug.
- Afzonderlijke handlers op één promise ontvangen de oorspronkelijke uitkomst.
- Als het opruimen in finally slaagt, blijft de eerdere uitkomst behouden; als het mislukt, vervangt de fout die uitkomst.
3.3 Schakel promises aaneen tot een reeks
Een promiseketen maakt de uitvoer van de ene handler tot invoer van de volgende. Elke schakel moet de waarde of promise teruggeven die latere schakels nodig hebben.
Geef waarden door de keten
Promise.resolve(3)
.then(count => count * 4)
.then(total => total + 2)
.then(total => console.log(total));
De eerste handler ontvangt 3 en geeft 12 terug. De tweede ontvangt 12 en geeft 14 terug. De laatste handler logt “14”. Een pijlfunctie met een expressie geeft die automatisch terug; een functielichaam met accolades heeft een expliciete return nodig om een waarde door te geven.
Geef een promise terug voor een afhankelijke stap
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makeCode() {
return wait(100).then(() => "P4");
}
function makeLabel(code) {
return wait(200).then(() => "Parcel " + code);
}
makeCode()
.then(code => makeLabel(code))
.then(label => console.log(label));
makeCode levert na ongeveer 100 ms “P4”. De volgende handler geeft de promise van makeLabel terug. De keten volgt die promise, wacht nog ongeveer 200 ms en geeft “Parcel P4” door aan de laatste handler. De aanroeper ontvangt een label, geen promise die in de waarde is genest.
Zoek een verbroken schakel
Vergelijk die middelste handler met deze:
.then(code => {
makeLabel(code);
})
Die start makeLabel, maar geeft niets terug. De nieuwe promise wordt succesvol voltooid met undefined zonder op het label te wachten. De volgende handler ontvangt daarom undefined na de stap die de code maakt, terwijl het voorbereiden van het label afzonderlijk doorgaat. Ook fouten uit dat afzonderlijke werk gaan niet meer door deze buitenste keten.
Herstel dit met return makeLabel(code); binnen de accolades, of verwijder de accolades om de expressie terug te geven. Dezelfde regel geldt wanneer een handler een pauze start: .then(() => wait(200)) wacht; .then(() => { wait(200); }) doet dat niet.
Maak de nesting vlak en bewaar benodigde waarden
Als je een andere then diep binnen de eerste handler zet, kan opnieuw nesting zoals bij callbacks ontstaan. Geef de binnenste promise terug en zet de volgende handler op het buitenste niveau. Zo blijft er één pad voor timing, waarden en fouten.
Elke handler heeft een eigen lokaal bereik. Als een latere stap zowel de code als het label nodig heeft, geef je een object terug dat beide bevat:
makeCode()
.then(code => makeLabel(code).then(label => ({ code, label })))
.then(pair => console.log(pair.code + ": " + pair.label));
Deze kleine binnenste handler bouwt de gecombineerde waarde op en wordt teruggegeven, zodat die verbonden blijft. Een asynchrone functie kan beide resultaten ook in gewone lokale variabelen bewaren.
Een afwijzing slaat latere handlers voor succesvolle voltooiing over totdat een handler voor afwijzing wordt bereikt. Een afsluitende catch kan fouten uit alle verbonden stappen erboven afhandelen. Afhankelijk werk hoort in deze reeks; onafhankelijk werk kan samen beginnen met Promise.all.
Belangrijkste punten
- Geef de waarde terug die de volgende handler nodig heeft.
- Als je een promise teruggeeft, wacht de keten op de uitkomst daarvan.
- Een pijlfunctie met een functielichaam tussen accolades heeft return nodig.
- Houd afhankelijk werk en de bijbehorende fouten verbonden met de buitenste keten.
- Geef gecombineerde gegevens door wanneer latere stappen eerdere lokale waarden nodig hebben.
4Schrijf asynchrone code met async en await
Met asynchrone functies kun je stappen met promises schrijven in de volgorde waarin je wilt dat ze plaatsvinden. Waarden kunnen in benoemde variabelen blijven staan en await markeert de punten waarop de functie pauzeert. Leer hoe die pauze de aanroeper beïnvloedt, zet oudere code om naar deze vorm en herken fouten die een promise achterlaten waar een waarde nodig is. Let vooral op het gedrag van lussen, want een asynchrone callback laat de aanroeper niet vanzelf wachten.
4.1 Schrijf asynchrone functies en gebruik await
Een asynchrone functie geeft altijd een promise terug. Daarbinnen pauzeert await die functie totdat een waarde beschikbaar is, zodat afhankelijke stappen van boven naar beneden lezen.
Geef een waarde terug via een promise
async function getCount() {
return 6;
}
getCount().then(count => console.log(count));
Een aanroep van getCount() geeft een promise die succesvol wordt voltooid met 6. De handler logt “6”. Als de functie een fout opwerpt, wordt de teruggegeven promise afgewezen. Als de functie een andere promise teruggeeft, volgt de asynchrone functie de uiteindelijke uitkomst van die promise.
Je kunt een asynchrone functie declareren met async function name(), een asynchrone pijlfunctie zoals const name = async () => {} of een asynchrone objectmethode. In alle gevallen wordt op dezelfde manier een promise teruggegeven.
Pauzeer met await
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function showSteps() {
console.log("Step one");
await wait(300);
console.log("Step two");
}
showSteps();
console.log("Caller continues");
De uitvoer begint met “Step one” en daarna “Caller continues”. Ongeveer 300 ms later wordt “Step two” gelogd. De aanroep van de functie voert het functielichaam synchroon uit tot de eerste await. Door de pauze gaat de controle terug naar de aanroeper. Zodra de promise succesvol wordt voltooid, gaat de rest van showSteps verder via de microtaakwachtrij.
Await pauzeert de eigen functie, niet de hele thread. Andere taken die klaarstaan kunnen tijdens het wachten worden uitgevoerd. Een grote synchrone berekening vóór of na de await kan de thread nog steeds blokkeren zolang die berekening loopt.
Lees een waarde of ontvang een fout
Een succesvol voltooide promise levert de waarde: const count = await getCount(); stelt count in op 6. Een afgewezen promise werpt de reden op de regel met await op. Plaats try en catch rond de bewerking waarop wordt gewacht als deze functie die fout moet afhandelen.
await accepteert ook gewone waarden. In const count = await 6; is het resultaat nog steeds 6, maar de voortzetting van de functie wordt uitgesteld. Die wordt niet synchroon alleen omdat er geen bewerking was waarop gewacht moest worden.
Controleer waar await hoort
Gebruik await binnen de asynchrone functie waarvan het functielichaam de await bevat. Het is ook toegestaan op het hoogste niveau van een JavaScript-module. Een gewoon functielichaam mag geen await bevatten; een buitenste asynchrone functie maakt een geneste gewone callback niet asynchroon.
Een aanroeper ontvangt nog steeds een promise. Om de uiteindelijke waarde te gebruiken, wacht je erop met await vanuit een andere asynchrone functie of koppel je then. Zo slaat const total = getCount(); een promise op; het slaat geen 6 op.
Een eventhandler kan zelf asynchroon zijn. De handler kan met await op een voorbereidende stap wachten voordat de pagina wordt bijgewerkt. Geef die actie een eigen foutafhandeling, want de eventafhandeling wacht niet op de promise die de handler teruggeeft. Op het hoogste niveau van een programma geeft een aanroep zoals showSteps().catch(console.error) een onverwachte fout een handler.
Belangrijkste punten
- Een asynchrone functie geeft een promise terug voor de retourwaarde of opgeworpen fout.
- Code vóór de eerste await wordt tijdens de aanroep uitgevoerd.
- Na await gaat de functie later verder met een waarde of wordt een reden voor afwijzing opgeworpen.
- Await hoort binnen een asynchrone functie of op het hoogste niveau van een module.
4.2 Herschrijf promiseketens en callback-API's met async en await
Een promiseketen kan een asynchrone functie worden zonder dat de afhankelijkheden veranderen. Elk resultaat krijgt een lokale variabele en elke stap waarop met await wordt gewacht, begint pas nadat het eerdere resultaat dat die nodig heeft klaar is.
Vervang handlers door toewijzingen met await
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getUser(id) {
return wait(100).then(() => ({ id, name: "Nia" }));
}
function getOrders(user) {
return wait(200).then(() => [user.name + "-1", user.name + "-2"]);
}
async function showOrders(id) {
const user = await getUser(id);
const orders = await getOrders(user);
console.log(user.name + ": " + orders.length + " orders");
}
showOrders(8).catch(console.error);
De eerste await levert een gebruiker. Pas dan kan getOrders(user) beginnen. Na in totaal ongeveer 300 ms logt de functie “Nia: 2 orders”. Zowel user als orders is binnen bereik voor die laatste regel. In een keten zou de parameter van elke handler tot een afzonderlijk lokaal bereik behoren.
De overeenkomstige keten zou getOrders(user) vanuit de eerste handler teruggeven. Als je die met await herschrijft, maak je het wachten expliciet terwijl de volgorde behouden blijft. Een afsluitende catch in de keten wordt een try/catch rond de stappen waarop met await wordt gewacht; finally wordt een opruimblok.
Zet een callback-API om in een promise-API
Als je async toevoegt aan een omhullende functie, wordt de vertraagde waarde van een callback-API niet vanzelf de retourwaarde. Verbind de callback met het afhandelen van de promise:
function readTag(id, callback) {
setTimeout(() => {
if (id <= 0) callback(new Error("Invalid tag"));
else callback(null, "Tag " + id);
}, 100);
}
function readTagAsync(id) {
return new Promise((resolve, reject) => {
readTag(id, (err, tag) => {
if (err) {
reject(err);
return;
}
resolve(tag);
});
});
}
Binnen een asynchrone functie geeft const tag = await readTagAsync(8); “Tag 8”. Als je de functie in plaats daarvan met 0 aanroept, wordt bij de await “Invalid tag” opgeworpen. Bij succes wordt de uitkomst van de promise van de omhullende functie vastgelegd; bij een fout wordt die afgewezen. De aanpassing verandert hoe de uitkomst wordt geleverd, niet wat de onderliggende bewerking doet.
Deze omzetting heet omzetting naar een promise-API. Voor gewone API's met één resultaat en de fout als eerste argument biedt Node.js ook util.promisify; veel ingebouwde modules hebben al varianten met promises, zoals fs/promises.
Houd één duidelijk pad aan
Timers worden pauzes via een hulpfunctie wait die een promise teruggeeft. Een asynchrone lus kan een stap loggen, met await op de pauze wachten en dan de volgende iteratie beginnen. Met callbacks zou de volgende stap binnen de vorige callback moeten worden ingepland.
Promises, ketens en asynchrone functies werken samen: een asynchrone functie kan met await op een keten wachten en een aanroeper kan then koppelen aan de promise die een asynchrone functie teruggeeft. Gebruik binnen een functie de vorm die afhankelijkheden en foutafhandeling makkelijk te volgen maakt. Een extra Promise-constructor is nuttig om een callback aan te passen, maar bestaande bewerkingen die promises teruggeven kun je direct met await afwachten.
Belangrijkste punten
- Elke afhankelijke stap in een keten kan een toewijzing met await worden.
- Lokale variabelen houden eerdere resultaten later in de asynchrone functie beschikbaar.
- Zet callbacks om naar een promise-API door fouten aan reject en resultaten aan resolve te koppelen.
- Op bestaande promise-API's kun je direct met await wachten.
4.3 Voorkom veelgemaakte fouten met async en await
Asynchrone code kan er synchroon uitzien terwijl er nog steeds promises tussen functies worden doorgegeven. Veel fouten ontstaan doordat je de uiteindelijke waarde te vroeg leest of aanneemt dat een hulpfunctie wacht op callbacks die ze negeert.
Wacht met await op de waarde voordat je die gebruikt
async function getQuantity() {
return 18;
}
async function total() {
const quantity = await getQuantity();
console.log(quantity * 3);
}
total().catch(console.error);
Dit logt “54”. Zonder await zou quantity een promise bevatten. Die promise met 3 vermenigvuldigen levert NaN op, geen uitgestelde berekening die zichzelf automatisch bijwerkt.
Een voorwaarde heeft dezelfde valkuil. Een asynchrone controle die false teruggeeft, geeft nog steeds een promiseobject terug en objecten zijn truthy. Gebruik if (await allowed()) binnen een asynchrone functie wanneer de tak afhangt van de uiteindelijke booleaanse waarde. Met if (allowed()) test je het object zelf.
forEach verzamelt geen promises van callbacks
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function labelAll(ids) {
ids.forEach(async id => {
await wait(100);
console.log("Label " + id);
});
console.log("Labels finished");
}
labelAll([4, 9]).catch(console.error);
“Labels finished” verschijnt vóór de labels. forEach roept beide callbacks aan, negeert de promises die ze teruggeven en rondt de eigen synchrone iteratie af. Zelfs await ids.forEach(...) zou op undefined wachten, niet op het werk van de callbacks. Een fout binnen die genegeerde promises valt ook buiten de promise die labelAll teruggeeft.
Schrijf voor verwerking op volgorde een gewone lus:
for (const id of ids) {
await labelOne(id);
}
console.log("Labels finished");
Hier is labelOne een bewerking die een promise teruggeeft. Elk item begint nadat het vorige klaar is. Gebruik voor onafhankelijke items await Promise.all(ids.map(id => labelOne(id))); vóór de laatste loginstructie. map verzamelt de teruggegeven promises en Promise.all geeft de aanroeper één promise voor hun voltooiing.
Onderscheid onafhankelijkheid van syntaxis
Als je direct met await op elke bewerking wacht, zijn de wachttijden opeenvolgend. Voor onafhankelijke wachttijden van 1000, 2000 en 1500 ms duurt dat ongeveer 4500 ms. Als je ze alle drie start en combineert, duurt het ongeveer 2000 ms. Een stap die een eerder resultaat nodig heeft, moet daar nog steeds op wachten.
Houd fouten verbonden
Een geneste functie heeft een eigen sleutelwoord async nodig om await te gebruiken, maar dat sleutelwoord alleen laat de aanroeper niet met await op het resultaat wachten. Controleer beide kanten: geeft de callback een promise terug en gebruikt de aanroeper die?
Koppel aan werk dat bewust wordt gestart zonder op voltooiing te wachten een handler voor afwijzing, zoals backgroundTask().catch(console.error). Controleer ook returns in asynchrone omhullende functies: async function prepare() { await wait(100); } wordt succesvol voltooid met undefined. Op een stap wachten en het resultaat ervan teruggeven zijn afzonderlijke beslissingen.
Geef resultaten van de herstelde groep terug
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function prepareItem(id) {
await wait(50);
return "Item " + id;
}
async function prepareAll(ids) {
return await Promise.all(ids.map(id => prepareItem(id)));
}
prepareAll([6, 2]).then(items => console.log(items.join(", ")));
Elke map-callback geeft een promise terug en map bewaart die promises in een array. Promise.all wacht op beide en levert ["Item 6", "Item 2"]. De functie prepareAll geeft die array via een eigen promise terug, zodat de aanroeper “Item 6, Item 2” logt nadat de groep klaar is.
Als je de return van prepareAll verwijdert, wordt nog steeds op de groep gewacht, maar ontvangt de aanroeper undefined. Als je map vervangt door forEach, gaat de array met promises verloren. Controleer verzamelen, wachten en teruggeven afzonderlijk wanneer je een actie met meerdere items herstelt.
Belangrijkste punten
- Wacht met await voordat je een uiteindelijk getal of een uiteindelijke booleaanse waarde gebruikt.
- forEach negeert promises die asynchrone callbacks teruggeven.
- Gebruik for...of voor opeenvolgend werk of map met Promise.all voor onafhankelijk werk.
- Een geneste callback heeft een eigen sleutelwoord async nodig.
- Koppel werk waarop bewust niet met await wordt gewacht aan een handler voor afwijzing.
5Handel fouten in asynchrone code af
Een mislukte bewerking maakt deel uit van de besturingsstroom van het programma. De code moet beslissen of ze herstelt, een nieuwe poging doet, de fout meldt of die doorgeeft aan een aanroeper die kan beslissen. Volg fouten door promiseketens en aanroepen waarop met await wordt gewacht en gebruik daarna opruimcode die bij beide uitkomsten wordt uitgevoerd. Plaats ten slotte foutafhandeling aan de grens van een actie, zodat de gebruiker een nuttige melding krijgt en je de technische oorzaak behoudt.
5.1 Handel afwijzingen in promiseketens af
Een afwijzing van een promise volgt de verbonden keten totdat een handler voor afwijzing die afhandelt. Als je begrijpt wat die handler teruggeeft, weet je of de keten is hersteld of nog steeds mislukt.
Volg een afwijzing langs handlers voor succes
Promise.resolve(8)
.then(value => {
throw new Error("Count rejected");
})
.then(value => console.log("Unused: " + value))
.catch(err => {
console.log(err.message);
return 2;
})
.then(value => console.log("Using " + value));
De opgeworpen fout wijst de nieuwe promise af die de eerste then teruggeeft. De volgende handler voor succesvolle voltooiing wordt overgeslagen, dus “Unused” wordt nooit gelogd. De catch logt “Count rejected” en geeft 2 terug. Door die retourwaarde wordt de volgende schakel succesvol voltooid, zodat de laatste handler “Using 2” logt.
Een expliciet afgewezen promise en een fout die binnen een handler wordt opgeworpen, komen op hetzelfde afwijzingspad terecht. Daarom kan één afsluitende catch fouten uit meerdere eerdere verbonden stappen afhandelen.
Kies herstel of doorgifte
Een handler voor afwijzing die een waarde teruggeeft, herstelt met die waarde. Niets teruggeven herstelt ook, maar met undefined. Een fout loggen is daarom niet hetzelfde als de keten afgewezen laten:
Promise.reject(new Error("Label missing"))
.catch(err => console.log(err.message))
.then(value => console.log(value));
De laatste handler ontvangt undefined. Als latere code een echt label nodig heeft, is dat stille herstel waarschijnlijk onjuist. Geef een bruikbare terugvalwaarde terug of werp de fout opnieuw op.
function reportAndRethrow(err) {
console.error(err);
throw err;
}
Als je deze functie in catch gebruikt, wordt de fout gelogd en blijft de volgende promise afgewezen met dezelfde reden. Een aanroeper verderop in de keten kan beslissen wat er moet gebeuren.
Plaats de handler na het werk dat die beschermt
Een catch handelt fouten erboven af. Als die herstelt, worden latere stappen normaal uitgevoerd en hebben nieuwe fouten daarin een latere handler nodig. Een handler in het midden kan geen fouten opvangen die nog niet zijn opgetreden.
Er is ook een belangrijk verschil tussen .then(success, failure) en .then(success).catch(failure). De twee handlers in één then reageren op de uitkomst van de binnenkomende promise. De handler voor fouten vangt geen fout op die door de bijbehorende handler voor succes wordt opgeworpen. Die fout hoort bij de nieuwe promise die de aanroep teruggeeft, dus is verderop een catch nodig.
Behoud nuttige fouten
Wijs af met Error-objecten, zodat handlers err.message en err.name kunnen lezen. Een string opwerpen of ermee afwijzen is toegestaan, maar code die de velden van een Error verwacht, ontvangt die niet. Behoud de oorspronkelijke fout wanneer je een fout doorgeeft, tenzij extra context de aanroeper helpt te begrijpen welke bewerking is mislukt.
Belangrijkste punten
- Afwijzingen slaan handlers voor succesvolle voltooiing over totdat een handler voor afwijzing wordt bereikt.
- Een waarde teruggeven vanuit catch herstelt de keten; niets teruggeven herstelt met undefined.
- Werp een fout opnieuw op om die verder door de keten te laten gaan.
- Een catch beschermt verbonden stappen die eraan voorafgaan.
- Een handler voor afwijzing in then kan geen fout opvangen die de bijbehorende handler voor succes opwerpt.
5.2 Gebruik try, catch en finally met await
Met try, catch en finally kan een asynchrone functie fouten afhandelen op het punt waar ze er met await op wacht. Gebruik deze structuur om resultaatverwerking, herstel en opruimen bij elkaar te houden.
Een fout draagt de controle over
Een Error-object beschrijft een fout. new Error("Label missing") heeft het bericht “Label missing” en de naam “Error”; ontwikkelaarstools kunnen ook het aanroepspoor tonen. Ingebouwde fouten hebben typen zoals TypeError.
throw stopt op dat punt de normale uitvoering en draagt de controle over aan de dichtstbijzijnde toepasselijke catch. Code na de afhandeling kan nog steeds worden uitgevoerd wanneer de functie haar eigen fout opvangt. Werp Error-objecten op zodat latere handlers nuttige velden ontvangen.
Wacht met await binnen het beschermde blok
async function run() {
console.log("Working");
try {
await Promise.reject(new Error("Device busy"));
console.log("Complete");
} catch (err) {
console.log("Failed: " + err.message);
} finally {
console.log("Controls enabled");
}
}
run().catch(console.error);
De afgewezen promise werpt op de await-regel een fout op. De rest van try wordt overgeslagen. De uitvoer is “Working”, “Failed: Device busy” en daarna “Controls enabled”. Een geslaagde bewerking zou “Complete” bereiken en nog steeds dezelfde opruimcode uitvoeren.
Gebruik finally voor een laadindicator, knopstatus of andere voorziening die na beide uitkomsten moet worden hersteld. Het blok wordt zelfs uitgevoerd wanneer try of catch een return-instructie heeft. Zorg dat opruimcode geen fout opwerpt, tenzij die nieuwe fout bewust de eerdere uitkomst moet vervangen. Een return in finally kan een eerdere return of opgeworpen fout overschrijven, dus vermijd die bij gewoon opruimen.
Een promise teruggeven verschilt hier van erop wachten met await
async function readSetting() {
try {
return await Promise.reject(new Error("Missing setting"));
} catch {
return "light";
}
}
readSetting().then(value => console.log(value));
Dit logt “light”. De functie wacht binnen try, dus bereikt de afwijzing de lokale catch. Als je await verwijdert, wordt de afgewezen promise direct teruggegeven; de functie neemt de afwijzing over, maar de lokale catch handelt die latere uitkomst niet af. Een aanroeper zou die moeten opvangen.
Geef elke fout een passende grens
Eén try kan een groep awaits omvatten wanneer de hele actie moet stoppen als een stap mislukt. Geef een optionele stap een kleinere grens wanneer die zelfstandig kan herstellen. Zo gebruikt const settings = await loadSettings().catch(() => ({ theme: "light" })); een standaardobject en gaat de functie verder.
Een variabele die binnen try is gedeclareerd, heeft blokbereik. Als code na het blok die nodig heeft, declareer dan let result; vóór try, of rond het werk dat van het resultaat afhangt binnen try af. Verplaats een waarde niet buiten haar bereik alleen om een laatste loginstructie op een gewenste plaats te houden.
Laat de afwijzing de aanroeper bereiken als de huidige functie niet kan herstellen. Als je die alleen opvangt om die te verbergen, kan de aanroeper geen nuttige beslissing nemen.
Belangrijkste punten
- Afwijzingen waarop met await wordt gewacht, werpen een fout op bij de await-regel.
- Catch kan herstellen, melden of opnieuw opwerpen.
- Finally wordt na beide uitkomsten uitgevoerd, ook bij een return.
- Gebruik return await binnen try wanneer de lokale catch de afwijzing van de teruggegeven bewerking moet afhandelen.
- Resultaten met blokbereik blijven binnen hun try-blok.
5.3 Houd fouten zichtbaar en zorg voor passend herstel
Een actie heeft een grens voor foutafhandeling nodig: een plaats die de uiteindelijke fout ontvangt en een reactie kiest. Zonder die grens kan een promise worden afgewezen terwijl niemand de uitkomst afhandelt.
Handel het laatste pad af
Een onafgehandelde afwijzing is een afwijzing die nog geen handler heeft wanneer de uitvoeringsomgeving controleert. Afhandeling kan met catch, het argument voor afwijzing van then of try/catch rond await. Een browser meldt het probleem en kan unhandledrejection activeren. Node.js stopt bij het normale beleid standaard bij onafgehandelde afwijzingen, dus als je er een onbehandeld laat, kan een programma eindigen.
Een actie zoals main() of een asynchrone klikhandler moet de uiteindelijke promise afhandelen. Onderliggende functies kunnen fouten naar boven doorgeven, maar het pad moet ergens eindigen waar wordt besloten wat te doen. Een globale listener voor afwijzingen kan helpen gemiste fouten te loggen; die kan niet voor elke actie het beste herstel kiezen.
Een lege catch verzwijgt de fout. Log technische details met console.error(err) en toon een duidelijke melding als de actie van de gebruiker is mislukt. Een melding zoals “Het label kon niet worden opgeslagen. Probeer het zo nog eens.” geeft de gebruiker een handelingsmogelijkheid zonder een aanroepspoor te tonen.
Voeg context toe zonder de oorzaak te verliezen
async function prepareLabel() {
try {
return await Promise.reject(new Error("Device offline"));
} catch (err) {
throw new Error("Could not prepare label", { cause: err });
}
}
prepareLabel().catch(err => {
console.log(err.message);
console.log(err.cause.message);
});
Dit logt “Could not prepare label” en “Device offline”. Het buitenste bericht benoemt de mislukte actie. De eigenschap cause bewaart de onderliggende fout voor diagnose. De optie cause van Error maakt deel uit van ECMAScript 2022.
Probeer een nieuwe bewerking opnieuw, met een limiet
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function withRetry(task, attempts) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
return await task();
} catch (err) {
if (attempt === attempts) throw err;
await wait(200);
}
}
}
Roep dit aan met een functie die een nieuwe poging start en een positief geheel getal als limiet. Als task twee keer mislukt en bij de derde aanroep slaagt, stopt de functie bij een limiet van vier na de derde aanroep en geeft ze de waarde daarvan terug. Als alle vier de pogingen mislukken, wordt de vierde fout opnieuw opgeworpen. De vertraging vindt alleen tussen pogingen plaats.
return await houdt een afwijzing binnen deze try, waar catch een nieuwe poging kan doen. Als je één al afgewezen promise doorgeeft in plaats van een taakfunctie, wordt het werk niet opnieuw gestart.
Probeer opnieuw wanneer een fout tijdelijk kan zijn. Gebruik een terugvaloptie wanneer de functie met standaardgegevens kan werken. Stop en informeer de gebruiker wanneer de actie niet kan slagen. Als een onderliggende functie die keuze niet kan maken, behoud dan de fout en laat de aanroeper beslissen.
Belangrijkste punten
- Elke asynchrone actie heeft een laatste handler voor fouten nodig.
- Een lege catch verbergt nuttige informatie over fouten.
- Een cause van een Error bewaart de oorspronkelijke fout en voegt tegelijk context toe.
- Doe een nieuwe poging door een taak opnieuw aan te roepen, met een eindige limiet voor het aantal pogingen.
- Kies een nieuwe poging, een terugvaloptie, stoppen of doorgifte passend bij de fout.
6Voer asynchrone taken parallel uit
Sommige bewerkingen hebben eerdere resultaten nodig; andere kunnen samen beginnen. Bepaal die afhankelijkheid voordat je kiest waar je await plaatst. Je combineert onafhankelijke bewerkingen, houdt resultaten in de juiste volgorde en kiest of je programma elk succes, elke uitkomst, de eerste afhandeling of het eerste succes nodig heeft. Elke keuze verandert wat een fout betekent en hoe lang de aanroeper wacht.
6.1 Start taken tegelijk en wacht met Promise.all
Onafhankelijke asynchrone bewerkingen kunnen tegelijk wachten. De keuze om ze samen te starten hangt af van de gegevens die ze nodig hebben, niet van het toevoegen van het woord async.
Start onafhankelijke bewerkingen voordat je op alle wacht
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makePart(name, ms) {
return wait(ms).then(() => name);
}
async function assemble() {
const parts = await Promise.all([
makePart("cover", 1000),
makePart("pages", 2000),
makePart("label", 1500)
]);
console.log(parts.join(", "));
}
assemble().catch(console.error);
De drie aanroepen starten voordat Promise.all wacht. Ongeveer 2000 ms later bevat de array ["cover", "pages", "label"]. Als je op elke aanroep zou wachten voordat je de volgende start, zou dat ongeveer 4500 ms duren. Timervertragingen zijn bij benadering, dus de werkelijk verstreken tijd kan langer zijn.
Hier betekent parallel dat wachttijden overlappen. De promisehandlers worden nog steeds één voor één op de hoofdthread uitgevoerd. Als je drie zware synchrone functies binnen de array zou aanroepen, zouden die berekeningen na elkaar worden uitgevoerd; Promise.all zou ze niet naar afzonderlijke threads verplaatsen.
Resultaten behouden hun invoerpositie
Het label is eerder klaar dan de pagina's, maar blijft op de derde plaats in de array. Promise.all behoudt de invoervolgorde, niet de volgorde van voltooiing. Met destructurering kun je die posities een naam geven:
const [cover, pages] = await Promise.all([
makePart("cover", 1000),
makePart("pages", 2000)
]);
Gebruik die code binnen een asynchrone functie. Voor een verzameling start ids.map(id => loadItem(id)) per ID één bewerking die een promise teruggeeft en geeft de array met promises aan Promise.all. Wacht niet binnen een lus met await op elk item als ze allemaal onafhankelijk zijn en moeten overlappen.
Een fout beëindigt het gezamenlijke wachten
Promise.all wordt afgewezen zodra een invoer wordt afgewezen. Als één onderdeel na ongeveer 300 ms mislukt, kan een catch die reden dan ontvangen; die hoeft niet op het langzaamste geslaagde onderdeel te wachten.
De andere bewerkingen blijven lopen. Hun neveneffecten kunnen nog steeds optreden, maar de gecombineerde promise zal niet later met hun waarden succesvol worden voltooid. Deze regel, direct afwijzen bij de eerste fout, is nuttig wanneer de volgende stap elk resultaat nodig heeft.
Houd rekening met afhankelijkheden en koppel handlers op tijd
Als een bewerking een gebruiker nodig heeft die door een andere bewerking wordt geleverd, haal je eerst de gebruiker op. De latere aanroepen kunnen alleen worden gecombineerd als ze, zodra die gebruiker bestaat, onafhankelijk van elkaar zijn.
Als je twee promises start en er afzonderlijk met await op wacht, overlappen hun wachttijden, maar de tweede kan worden afgewezen voordat de code de await daarvoor bereikt. Als die nog geen handler heeft wanneer de uitvoeringsomgeving controleert, kan een onafgehandelde afwijzing worden gemeld. Door de promises direct met Promise.all te combineren, koppel je afhandeling aan beide invoeren en geef je de aanroeper één pad voor fouten.
Gewone waarden zijn toegestaan tussen de invoeren en behouden hun positie. Promise.all([]) is al succesvol voltooid met een lege array. Een handler die je eraan koppelt, wordt nog steeds asynchroon uitgevoerd.
Belangrijkste punten
- Start onafhankelijke bewerkingen samen om hun wachttijden te laten overlappen.
- Promise.all geeft resultaten terug in invoervolgorde.
- Eén afwijzing wijst de gecombineerde promise af zonder andere invoeren te annuleren.
- Afhankelijke stappen hebben nog steeds hun eerdere resultaten nodig.
- Combineer gestarte promises direct om hun foutpaden verbonden te houden.
6.2 Kies tussen Promise.allSettled, Promise.race en Promise.any
De juiste promisecombinator hangt af van wat als een nuttig resultaat telt. Bepaal of je alle successen, alle uitkomsten, de eerste afhandeling of het eerste succes nodig hebt.
Behoud gedeeltelijke resultaten met allSettled
Promise.allSettled wacht op elke invoer en geeft uitkomstobjecten terug in invoervolgorde. Een succes wordt { status: "fulfilled", value }; een fout wordt { status: "rejected", reason }. Een afgewezen invoer wijst de gecombineerde promise niet af.
Promise.allSettled([
Promise.resolve("Map ready"),
Promise.reject(new Error("Chart unavailable"))
]).then(results => {
for (const result of results) {
if (result.status === "fulfilled") console.log(result.value);
else console.log(result.reason.message);
}
});
Dit logt “Map ready” en “Chart unavailable”. Het is geschikt voor onafhankelijke panelen waarbij één werkend paneel nuttig blijft, ook als een ander mislukt. Lees value alleen bij een object voor succesvolle voltooiing en reason bij een object voor afwijzing.
Laat de eerste afhandeling beslissen met race
Promise.race volgt de eerste invoer die wordt afgehandeld, ongeacht of die succesvol wordt voltooid of wordt afgewezen. Een fout na 100 ms wint van een succes na 200 ms. Dit is nuttig voor een tijdslimiet:
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function timeout(ms) {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error("Time limit reached")), ms);
});
}
Promise.race([
wait(600).then(() => "Prepared"),
timeout(250)
]).then(console.log).catch(err => console.log(err.message));
De timer voor de tijdslimiet wordt als eerste afgewezen, dus de uitvoer is “Time limit reached”. De bewerking van 600 ms blijft lopen. Een race handelt de gecombineerde promise van de aanroeper af; die annuleert de verliezende bewerkingen niet.
Wacht op het eerste succes met any
Promise.any negeert mislukte invoeren zolang succes nog mogelijk is. Als één bewerking na 100 ms mislukt en een andere na 200 ms succesvol wordt voltooid, geeft de promise de laatste waarde. Een snellere afwijzing wint niet.
Als elke invoer wordt afgewezen, wijst any af met een AggregateError. De array errors bewaart de redenen voor afwijzing in invoervolgorde. Zo kan een aanroeper nagaan waarom elke poging is mislukt, ook als de fouten in een andere volgorde binnenkwamen.
Vergelijk de beslisregels
all: elke waarde is vereist; wijs af bij de eerste afwijzing.allSettled: elke uitkomst is vereist; wacht op alle uitkomstobjecten.race: de eerste afhandeling beslist, ook bij een fout.any: de eerste succesvolle voltooiing beslist; wijs alleen af als alles wordt afgewezen.
Zonder invoer worden all en allSettled succesvol voltooid met []. any wordt afgewezen met AggregateError omdat geen succes mogelijk is. race blijft in afwachting omdat niets die promise kan afhandelen.
Alle vier worden ondersteund in huidige browsers en Node.js. all en race kwamen in 2015 in ECMAScript, allSettled in 2020 en any in 2021.
Vergelijk één paar uitkomsten
Stel dat beide bewerkingen nu beginnen: de eerste invoer mislukt na ongeveer 100 ms, terwijl de tweede na ongeveer 300 ms succesvol wordt voltooid met “usable”. Als je elke combinator op datzelfde paar toepast, krijg je verschillende resultaten:
- All wordt na ongeveer 100 ms afgewezen met de fout van de eerste invoer, omdat elke invoer moet slagen.
- AllSettled wordt na ongeveer 300 ms succesvol voltooid met een object voor afwijzing gevolgd door een object voor succesvolle voltooiing, met behoud van de invoerposities.
- Race wordt na ongeveer 100 ms afgewezen omdat dat de eerste afhandeling is.
- Any wordt na ongeveer 300 ms succesvol voltooid met “usable”, omdat de eerdere fout geen succes kan leveren.
All en race worden in dit geval toevallig tegelijk afgewezen, maar gebruiken verschillende regels. Als de snelle eerste invoer zou slagen en de tweede invoer later zou mislukken, zou race het vroege succes behouden terwijl all bij de latere fout zou worden afgewezen. Kies op basis van de vereiste uitkomst, niet op basis van de timing van één voorbeeld.
Belangrijkste punten
- allSettled meldt elk succes en elke fout in invoervolgorde.
- race volgt de eerste afhandeling, ook bij afwijzing.
- any accepteert het eerste succes en wordt alleen afgewezen wanneer elke invoer wordt afgewezen.
- Een tijdslimiet met race laat verliezend werk doorlopen.
- race met lege invoer blijft in afwachting; any met lege invoer wordt afgewezen.
- Kies de combinator op basis van de uitkomst die de aanroeper daadwerkelijk nodig heeft.
Asynchrone JavaScript: voorbeelden
1Voorspel wat nu en wat later wordt uitgevoerd
14 stappen
- 1IntroductieOnderscheid starten van voltooien
Begin met het onderscheiden van directe aanroepen en geregistreerde callbacks. Een timer kan wachten zonder de hoofdthread te bezetten, maar de callback heeft die thread nog steeds nodig. Volg wat tijdens het script op de aanroepstapel wordt uitgevoerd voordat je verdergaat met werk in de wachtrij, een taak of een microtaak, omdat hun volgorde bepaalt wat je het eerst ziet.
- 2VoorbeeldRegistreer een callback
Lees dit script voordat je de uitvoer voorspelt:
console.log("request"); setTimeout(() => console.log("ready"), 200); console.log("continue");De eerste loginstructie wordt nu uitgevoerd. De timeraanroep registreert een functie en keert terug, zodat het script direct de laatste loginstructie bereikt. De loginstructie van de callback hoort bij een latere taak; door die functie te maken is ze nog niet aangeroepen.
- 3KernpuntWachten bezet de thread niet
De uitvoer begint met “request”, “continue”, gevolgd door “ready” na ongeveer 200 ms. De uitvoeringsomgeving beheert het wachten, maar de callback heeft nog steeds de hoofdthread nodig. Een lange lus kan de callback vertragen, ook nadat de timer klaar is.
- 4UitdagingKan de timer onderbreken?
Een script registreert een timer zonder vertraging en voert daarna een lange synchrone berekening uit. De timer is klaar voordat de berekening eindigt. Kan de callback tijdens die berekening worden uitgevoerd?
---reveal---
Nee. De berekening houdt de hoofdthread bezet totdat het huidige werk klaar is. Als de timer klaar is, komt de callback voor later in de wachtrij; de callback krijgt daardoor geen aparte uitvoeringsthread of toestemming om de berekening te onderbreken.
- 5UitlegHoud twee soorten later werk apart
Een timercallback wordt als taak uitgevoerd. Een promisehandler wordt als microtaak uitgevoerd. De aanroepstapel houdt bij welke aanroepen op dat moment worden uitgevoerd.
Na het huidige werk wordt bij een controlepunt voor microtaken de microtaakwachtrij afgewerkt voordat een volgende taak kan beginnen. Microtaken die tijdens het afwerken worden toegevoegd, komen in dezelfde wachtrij. Houd bij het volgen van een script afzonderlijke lijsten bij voor directe uitvoer, microtaken en timertaken.
- 6VoorbeeldBreng de wachtrijen in kaart
Voeg nu een promisehandler toe:
console.log("request"); setTimeout(() => console.log("timer"), 0); Promise.resolve().then(() => console.log("job")); console.log("continue");De directe uitvoer is “request”, “continue”. De promisehandler wacht als microtaak; de timercallback is een taak. Rond het huidige script af voordat je een van beide geregistreerde functies uitvoert.
- 7VoorbeeldWerk microtaken af vóór de volgende taak
Nadat het script klaar is, logt de microtaak “job”. Pas als de microtaakwachtrij leeg is, kan de klaarstaande timertaak “timer” loggen.
De volledige uitvoer is “request”, “continue”, “job”, “timer”. Een succesvol voltooide promise roept de handler niet synchroon aan en een timer zonder vertraging gaat niet vóór microtaken. De browser kan na het afwerken van deze microtaken renderen wanneer daar gelegenheid voor is.
- 8UitdagingPlaats een nieuw toegevoegde taak
Wat is de uitvoervolgorde?
Promise.resolve().then(() => console.log("a")) .then(() => console.log("c")); queueMicrotask(() => console.log("b")); console.log("now");---reveal---
“now”, “a”, “b”, “c”. Het script zet a vóór b in de wachtrij. Pas wanneer de handler van a terugkeert, komt de volgende handler in de keten klaar te staan, zodat c achter de al ingeplande taak b komt.
- 9UitlegKies een timer en bewaar de referentie
Gebruik
setTimeoutvoor één geplande aanroep ensetIntervalvoor herhaalde aanroepen. Beide geven een referentie terug die je kunt bewaren.const hint = setTimeout(() => console.log("Hint"), 800); clearTimeout(hint);Dit annuleert de callback voordat die wordt uitgevoerd. Geef bij een herhalende timer de referentie door aan
clearIntervalwanneer de herhaling moet eindigen. Geef een functie door om die in te plannen; als je die in de argumentenlijst aanroept, wordt ze direct uitgevoerd. - 10TipHoud rekening met vertraagde callbacks
Vertragingen zijn in milliseconden en geven een minimale wachttijd aan. Drukke code, tabbladen op de achtergrond en beperkingen van de browser kunnen callbacks verder vertragen. Plan een geneste eenmalige timer in na de huidige uitvoering; wacht ook met await op het asynchrone werk daarvan wanneer uitvoeringen niet mogen overlappen.
- 11VoorbeeldGebruik de juiste lusbinding
Met
letgeeft elke iteratie de callback een afzonderlijke index:for (let n = 0; n < 3; n++) { setTimeout(() => console.log(n), 50); }De callbacks loggen “0”, “1”, “2”. Als je let vervangt door var, delen ze één variabele, waarvan de waarde na de lus 3 is. Alle drie loggen dan “3”. De callback leest de binding wanneer die later wordt uitgevoerd.
- 12UitdagingStart een timer voor een rustperiode opnieuw
Een handler gebruikt een vertraging van 300 ms om uit te stellen tot na een rustperiode. Invoerevents vinden plaats op 0 ms, 80 ms en 170 ms en er komen geen events meer. Na ongeveer hoeveel tijd wordt de ene actie uitgevoerd?
---reveal---
Ongeveer 470 ms na het eerste event: 300 ms na het laatste event op 170 ms. Elk event annuleert de vorige wachtende timer en plant een nieuwe in. De eerder geplande acties worden niet ook uitgevoerd en een bezette thread kan de laatste actie verder vertragen.
- 13UitdagingVerbind de timingregels
Een timer is klaar, maar elke microtaak blijft een volgende microtaak in de wachtrij zetten. Waarom kunnen noch de timercallback, noch de volgende schermvernieuwing doorgaan?
---reveal---
De microtaakwachtrij raakt nooit afgewerkt, dus kan de eventloop niet doorgaan naar de volgende taak of gelegenheid om te renderen. De verstreken vertraging van de timer verandert die volgorde niet. Eindeloos microtaken inplannen kan de voortgang blokkeren, net als een lange lus.
- 14SamenvattingEen werkwijze die je kunt hergebruiken
Volg eerst directe code, werk daarna microtaken af en ga dan naar klaarstaande taken. Bewaar timerreferenties om te annuleren. Vertragingen zijn bij benadering; let bewaart iteratiewaarden, terwijl uitstellen tot na een rustperiode de vertraging voor die rustperiode opnieuw start.
2Lever een resultaat via een callback
9 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieGeef de aanroeper een volgende actie
- 2VoorbeeldProduceer de vertraagde waarde
- 3VerbandDe API bepaalt de timing
- 4UitdagingUit welke functie terugkeren?
- 5VoorbeeldMeld één van twee uitkomsten
- 6TipStop voordat je een mislukt resultaat leest
- 7UitdagingZoek de actieve grens voor foutafhandeling
- 8UitdagingVerbind resultaten en fouten tussen stappen
- 9SamenvattingVolg elke uitkomst naar de consument
3Bouw en verbind uitkomsten van promises
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieEén object voor een latere uitkomst
- 2VoorbeeldBouw een herbruikbare pauze
- 3KernpuntHoud toestand en vastlegging van de uitkomst apart
- 4UitdagingVang een latere fout correct af
- 5UitlegKoppel de juiste handler
- 6VoorbeeldTwee registraties behouden één waarde
- 7UitdagingLees een resultaat van opruimcode
- 8VoorbeeldBouw de eerste schakel
- 9VoorbeeldLaat de volgende schakel wachten
- 10UitdagingHerstel een losgekoppelde bewerking
- 11UitdagingVerbind opruimcode aan een afhankelijke keten
- 12SamenvattingControleer het teruggegeven pad
4Herschrijf een reeks met async en await
12 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBehoud promises, verander de leesvolgorde
- 2VoorbeeldEen asynchrone aanroep begint direct
- 3UitdagingLees de teruggegeven waarde
- 4VoorbeeldVerbind een callback aan een promise
- 5VoorbeeldHoud tussenresultaten binnen bereik
- 6UitdagingPast async alleen het resultaat aan?
- 7KernpuntTest de waarde, niet de promise
- 8VoorbeeldforEach wacht niet met await op callbacks
- 9TipKies een aanpak voor de lus
- 10UitdagingHerstel te vroege voltooiing
- 11UitdagingVerbind de aanpassing en de lus
- 12SamenvattingBekijk beide kanten van een asynchrone aanroep
5Houd fouten, herstel en opruimen verbonden
13 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBepaal wat een fout moet betekenen
- 2VoorbeeldBekijk hoe een catch herstelt
- 3KernpuntOok bij loggen wordt een retourwaarde gekozen
- 4UitdagingBehoud de mislukte uitkomst
- 5VoorbeeldWacht met await waar catch de fout kan zien
- 6TipKeer terug na lokale afhandeling
- 7UitdagingKies de lokale terugvaloptie
- 8VerbandSluit het foutpad van de actie af
- 9VoorbeeldBehoud context en de oorspronkelijke oorzaak
- 10VoorbeeldProbeer een nieuwe aanroep opnieuw, met een limiet
- 11UitdagingStop na succes
- 12UitdagingVerbind herstel aan opruimen
- 13SamenvattingGeef elke uitkomst een bestemming
6Kies wat een groep taken moet leveren
11 stappen
Sla deze les op om elke Walkthrough stap voor stap te volgen.
- 1IntroductieBegin met de vraag naar afhankelijkheid
- 2VoorbeeldStart beide bewerkingen samen
- 3KernpuntEen fout annuleert de groep niet
- 4UitdagingRespecteer één afhankelijkheid en laat de rest overlappen
- 5UitlegBehoud elke uitkomst wanneer onderdelen nuttig blijven
- 6VoorbeeldOnderscheid eerste afhandeling van eerste succes
- 7UitdagingKies voor een gedeeltelijk werkende pagina
- 8TipEen tijdslimiet beëindigt het wachten, niet de bewerking
- 9UitdagingWat kan een lege groep afhandelen?
- 10UitdagingVerbind starttiming en foutafhandeling
- 11SamenvattingKies de gewenste uitkomst
Asynchrone JavaScript: oefenvragen
Set 1: Volg de code van aanroepen tot callbacks10 Oefenvragen
Vraag 1 van 10
Toon antwoordVerberg antwoord
Vraag 2 van 10
Wat is de uitvoervolgorde?
console.log("open");
setTimeout(() => console.log("tick"), 0);
Promise.resolve().then(() => console.log("job"));
console.log("close");
Toon antwoordVerberg antwoord
Vraag 3 van 10
const reminder = setTimeout(showHint, 900);. Welke instructie annuleert de timer voordat die wordt uitgevoerd?Toon antwoordVerberg antwoord
Vraag 4 van 10
Wat logt deze lus nadat de callbacks zijn uitgevoerd?
for (var index = 2; index < 5; index++) {
setTimeout(() => console.log(index), 0);
}
Toon antwoordVerberg antwoord
Vraag 5 van 10
Hoeveel callbackaanroepen zijn voltooid wanneer deze code de loginstructie bereikt?
let calls = 0;
[3, 6, 9].map(n => {
calls++;
return n + 1;
});
console.log(calls);
Toon antwoordVerberg antwoord
Vraag 6 van 10
Wat bevat result wanneer de loginstructie wordt uitgevoerd?
function make() {
setTimeout(() => { return "badge"; }, 0);
}
const result = make();
console.log(result);
Toon antwoordVerberg antwoord
Vraag 7 van 10
Een callback met de fout als eerste argument ontvangt bij een fout een Error en geen resultaat. Wat voorkomt dat code voor succes na deze foutcontrole een ontbrekend item leest?
if (err) {
console.log(err.message);
// Add one statement here.
}
console.log(item.name);
Toon antwoordVerberg antwoord
Vraag 8 van 10
Wat is de uitvoervolgorde?
Promise.resolve().then(() => {
console.log("oak");
queueMicrotask(() => console.log("elm"));
}).then(() => console.log("ash"));
queueMicrotask(() => console.log("fir"));
console.log("pine");
Toon antwoordVerberg antwoord
Vraag 9 van 10
Een bewerking met één resultaat gebruikt dit timerlichaam. Wat gebeurt er wanneer id 0 is en welke wijziging herstelt dit?
if (id <= 0) callback(new Error("Invalid"));
callback(null, { id });
Toon antwoordVerberg antwoord
Vraag 10 van 10
Wat is de uitvoervolgorde?
console.log("outside");
const p = new Promise(resolve => {
console.log("executor");
resolve("value");
});
p.then(console.log);
console.log("registered");
Toon antwoordVerberg antwoord
Set 2: Geef waarden door via promises en await10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Welke waarde levert deze promise aan de handler voor succesvolle voltooiing?
const p = new Promise((resolve, reject) => {
resolve(14);
reject(new Error("Changed"));
resolve(28);
});
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Wat wordt gelogd?
const p = Promise.resolve(7);
p.then(n => n + 4);
p.then(n => console.log(n));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Wat wordt gelogd?
Promise.resolve(6)
.then(n => n - 2)
.then(n => n * 5)
.then(console.log);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
De functie wait(ms) geeft een promise terug die na ongeveer ms milliseconden succesvol wordt voltooid. Wat bereikt console.log en wanneer?
wait(120)
.then(() => { wait(480).then(() => "ready"); })
.then(console.log);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Wat voor soort waarde geeft een aanroep van deze functie terug?
async function count() { return 9; }
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Wat is de uitvoervolgorde?
async function show() {
console.log("inside");
await 0;
console.log("after await");
}
show();
console.log("caller");
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
done(err, value) één keer aan. Wat moet de omhullende functie die een promise teruggeeft doen wanneer err een Error is?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Wat wordt gelogd?
async function permitted() { return false; }
if (permitted()) console.log("enter");
else console.log("stop");
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
record(id) geeft een promise terug en kan worden afgewezen. Hoe moet deze functie ID's op volgorde verwerken, stoppen bij een fout en de aanroeper die fout laten opvangen?
async function saveAll(ids) {
ids.forEach(async id => { await record(id); });
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Welke waarde logt de laatste handler?
Promise.resolve(11)
.finally(() => 40)
.then(n => n + 2)
.finally(() => Promise.resolve(90))
.then(console.log);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 3: Handel fouten af en combineer onafhankelijke taken10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
"default" terug na een afwijzing te hebben ontvangen. Wat ontvangt de daaropvolgende then-handler?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Wat wordt gelogd?
Promise.resolve("start")
.then(() => { throw new Error("later"); },
() => console.log("same call"))
.catch(err => console.log(err.message));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
Wat is de uitvoervolgorde?
async function run() {
try {
await Promise.reject(new Error("busy"));
console.log("saved");
} catch (err) {
console.log(err.message);
} finally {
console.log("reset");
}
}
run();
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Wat logt de aanroeper?
async function read() {
try {
return Promise.reject(new Error("missing"));
} catch {
return "local";
}
}
read().then(console.log).catch(err => console.log(err.message));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
new Error("Report failed", { cause: original }). Welke eigenschap houdt de oorspronkelijke Error beschikbaar?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
"ok". Wat gebeurt er?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
await Promise.all(...) en beide slagen. Hoe lang duurt het gezamenlijke wachten ongeveer?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Promise.all([A, B, C])?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
"backup". Waarin verschillen race en any?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Set 4: Spoor fouten op in volledige asynchrone uitvoeringspaden10 Oefenvragen
Sla deze les op om elk antwoord te zien en elke set te oefenen.
Vraag 1 van 10
Wat wordt als eerste uitgevoerd wanneer deze instructie wordt geëvalueerd?
setTimeout(showMessage(), 500);
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 2 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 3 van 10
inner is nog in afwachting. Wat gebeurt hier als inner later wordt afgewezen?
const outer = new Promise((resolve, reject) => {
resolve(inner);
reject(new Error("replacement"));
});
outer.catch(err => console.log(err.message));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 4 van 10
Wat is de uitvoervolgorde?
async function step() {
console.log("a");
await null;
console.log("b");
queueMicrotask(() => console.log("c"));
}
setTimeout(() => console.log("d"), 0);
step();
Promise.resolve().then(() => console.log("e"));
console.log("f");
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 5 van 10
Wat logt de laatste handler?
Promise.reject(new Error("first"))
.catch(() => 4)
.then(n => { throw new Error("second " + n); })
.finally(() => 9)
.catch(err => console.log(err.message));
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 6 van 10
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 7 van 10
Waarom probeert deze functie een asynchroon afgewezen taak niet opnieuw?
async function retry(task) {
for (let n = 0; n < 3; n++) {
try { return task(); }
catch (err) { if (n === 2) throw err; }
}
}
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 8 van 10
Promise.race([]) wanneer er nooit invoer wordt toegevoegd?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 9 van 10
Promise.any([first, second]) uiteindelijk aan catch?Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Vraag 10 van 10
Wat logt de aanroeper?
async function value() {
try {
return await Promise.reject(new Error("lost"));
} catch {
return "fallback";
} finally {
console.log("cleanup");
}
}
value().then(console.log);
console.log("caller");
Toon antwoordVerberg antwoord
Sla deze les op om dit antwoord te zien.
Kom je niet uit je eigen opgave? Huiswerkhulp
Kernbegrippen
A
aanroepstapel
afgehandeld
afgewezen
afhankelijke bewerking
AggregateError
asynchrone bewerking
asynchrone callback
asynchrone functie
await
await op moduleniveau
B
bereik van een catch-afhandeling
blokkeren
C
callback
callback met de fout als eerste argument
callback voor afwijzing
callback voor succesvolle voltooiing
callbackhel
callbackregistratie bij een promise
D
direct afwijzen bij de eerste fout
doorgifte van afwijzingen
E
eenmalige timer
Error-object
eventloop
executorfunctie
F
finally-blok
finally-callback
foutevent
foutoorzaak
G
gelijktijdige voortgang
genegeerde promise uit een callback
geneste eenmalige timer
H
herhalende timer
herstel
hoofdthread
I
in afwachting
invoervolgorde
M
microtaak
N
nieuwe poging
O
omhullende functie die een promise teruggeeft
omzetting naar een promise-API
onafgehandelde afwijzing
onafhankelijke bewerkingen
ononderbroken uitvoering tot voltooiing
ontbrekende return
opnieuw opwerpen
overname van de uitkomst van een andere promise
P
promise
Promise.all
Promise.allSettled
Promise.any
Promise.race
promiseketen
S
sequentiële verwerking
succesvol voltooid
synchrone callback
map is doorgegeven, verdubbelt elk element voordat de voltooide resultaatarray wordt teruggegeven.synchrone code
T
taak
terugvaloptie
timerreferentie
truthy-waarde
try-blok
tussenresultaat
U
uitstellen tot na een rustperiode
uitvoeringsomgeving
uitzondering opwerpen
V
vastgelegd
verdringing door microtaken
vergeten await
vervolgcode
verzwegen fout
vlakke promiseketen
voortijdig terugkeren
Over deze les
Door StudeliFout melden
Maak je eigen Studiemateriaal
Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.