Promise handlers .then/.catch/.finally er altid asynkrone.
Selv når et promise løses umiddelbart, vil koden på linjerne under .then/.catch/.finally stadig køre før disse handlers.
Her er et eksempel:
let promise = Promise.resolve();
promise.then(() => alert("promise færdig!"));
alert("kode færdig"); // denne kode vises først
Hvis du kører det, ser du kode færdig først, og så promise færdig!.
Det er mærkeligt, fordi promise er helt sikkert færdig fra starten.
Hvorfor aktiverede .then efterfølgende? Hvad sker der?
Microtasks queue
Asynkrone opgaver har brug for korrekt hÃ¥ndtering. Til det formÃ¥l specificerer ECMA-standarden en intern kø PromiseJobs, som ofte refereres til som âmicrotask queueâ (V8 term).
Som angivet i specifikationen:
- Køen er first-in-first-out: opgaver, der er sat i køen først, køres først.
- Udførelse af en opgave initieres kun, når intet andet er i gang.
Eller, sagt på en simpel måde, når et promise er færdigt, bliver dets .then/catch/finally handlers sat i køen; de bliver ikke udført endnu. Når JavaScript-motoren bliver ledig fra den nuværende kode, tager den en opgave fra køen og udfører den.
Det er derfor âkode færdigâ i eksemplet ovenfor vises først.
Promise handlers går altid gennem denne interne kø.
Hvis der er en kæde med flere .then/catch/finally, så udføres hver enkelt asynkront. Det vil sige, at de først sættes i køen, og derefter udføres, når den nuværende kode er færdig og tidligere satte handlers er færdige.
Hvad hvis rækkefølgen er vigtig for os? Hvordan kan vi få kode færdig til at vises efter promise færdig?
Simpelthen ved at putte det i køen med .then:
Promise.resolve()
.then(() => alert("promise færdig!"))
.then(() => alert("kode færdig"));
Nu er rækkefølgen som ønsket.
Uhåndteret afvisning
Husker du unhandledrejection eventen fra artiklen HÃ¥ndtering af fejl med promises?
Nu kan vi se præcis hvordan JavaScript finder ud af, at der er en uhåndteret afvisning.
En âuhÃ¥ndteret afvisningâ opstÃ¥r, nÃ¥r en promise fejl ikke hÃ¥ndteres i slutningen af microtask-køen.
Normalt, hvis vi forventer en fejl, tilføjer vi .catch til promise-kæden for at håndtere den:
let promise = Promise.reject(new Error("Promise fejlet!"));
promise.catch(err => alert('fanget'));
// kører ikke: fejlen er håndteret
window.addEventListener('unhandledrejection', event => alert(event.reason));
Men hvis vi glemmer at tilføje .catch, sÃ¥ udløser motoren eventet, efter at microtask-køen er tom, og den ser, at der er en promise i âafvistâ tilstand:
let promise = Promise.reject(new Error("Promise fejlet!"));
// Promise fejlet!
window.addEventListener('unhandledrejection', event => alert(event.reason));
Hvad hvis vi håndterer fejlen senere? Sådan her:
let promise = Promise.reject(new Error("Promise fejlet!"));
setTimeout(() => promise.catch(err => alert('fanget')), 1000);
// Error: Promise fejlet!
window.addEventListener('unhandledrejection', event => alert(event.reason));
Hvis vi kører koden vil vi se Promise fejlet! først og derefter fanget.
Hvis vi ikke kendte til microtasks-køen, kunne vi undre os: âHvorfor kørte unhandledrejection handleren? Vi fangede og hÃ¥ndterede fejlen!â
Men nu forstÃ¥r vi, at unhandledrejection genereres, nÃ¥r microtask-køen er tom: motoren undersøger promises, og hvis en af dem er i ârejectedâ tilstand, sÃ¥ udløser eventet.
I eksemplet ovenfor kan vi se et .catch blive tilføjet af setTimeout som ogsÃ¥ udløses â men det sker senere. PÃ¥ det tidspunkt er unhandledrejection allerede opstÃ¥et, sÃ¥ det ændrer ikke pÃ¥ noget.
Opsummering
HÃ¥ndtering af promises er altid asynkron, da alle promise-handlinger gÃ¥r gennem den interne âpromise jobsâ kø, ogsÃ¥ kaldet âmicrotask køâ (V8 term).
Så .then/catch/finally handlers kaldes altid efter, at den nuværende kode er færdig.
Hvis vi har brug for at garantere, at en del af koden bliver udført efter .then/catch/finally, kan vi tilføje den til en kædet .then kald.
I de fleste Javascript motorer, inklusiv browsere og Node.js, er konceptet microtasks tæt forbundet med âevent loopâ og âmacrotasksâ. Da disse ikke har en direkte relation til promises, er de behandlet i en anden del af tutorialen, i artiklen Event loop: microtasks and macrotasks.
Kommentarer
<code>-taggen, for flere linjer - omslut dem i<pre>-tag, for mere end 10 linjer - brug en sandbox (plnkr, jsbin, codepenâ¦)