Mikroúkoly

Obslužné nástroje pro příslib .then /.catch /.finally jsou vždy asynchronní.

I když je příslib okamžitě vyřešen, kód na řádcích níže .then /.catch /.finally bude stále spuštěn před těmito obslužnými rutinami.

Zde je ukázka:

let promise = Promise.resolve();

promise.then(() => alert("promise done!"));

alert("code finished"); // this alert shows first

Pokud jej spustíte, uvidíte code finished nejprve a poté promise done! .

To je zvláštní, protože slib je definitivně splněn od začátku.

Proč .then spustit poté? Co se děje?

Fronta mikroúloh

Asynchronní úlohy vyžadují řádnou správu. Za tímto účelem standard ECMA specifikuje interní frontu PromiseJobs , častěji označované jako „fronta na mikroúlohy“ (výraz V8).

Jak je uvedeno ve specifikaci:

  • Fronta je první dovnitř, první ven:úkoly zařazené do fronty jsou spuštěny jako první.
  • Provedení úlohy je zahájeno pouze tehdy, když není spuštěno nic jiného.

Nebo, jednodušeji řečeno, když je slib připraven, jeho .then/catch/finally manipulátoři jsou zařazeni do fronty; ještě nejsou popraveni. Když se engine JavaScriptu zbaví aktuálního kódu, vezme úlohu z fronty a provede ji.

Proto je „kód dokončen“ ve výše uvedeném příkladu zobrazen jako první.

Obsluhy slibů vždy procházejí touto interní frontou.

Pokud existuje řetězec s více .then/catch/finally , pak se každý z nich provede asynchronně. To znamená, že se nejprve zařadí do fronty a poté se provede, když je dokončen aktuální kód a jsou dokončeny dříve zařazené obslužné programy.

Co když pro nás na objednávce záleží? Jak můžeme vytvořit code finished objeví se za promise done ?

Jednoduše, stačí jej zařadit do fronty s .then :

Promise.resolve()
 .then(() => alert("promise done!"))
 .then(() => alert("code finished"));

Nyní je pořadí podle plánu.

Neošetřené odmítnutí

Pamatujte na unhandledrejection událost z článku Řešení chyb se sliby?

Nyní můžeme přesně vidět, jak JavaScript zjistí, že došlo k neošetřenému odmítnutí.

K „neošetřenému odmítnutí“ dochází, když chyba slibu není zpracována na konci fronty mikroúloh.

Obvykle, pokud očekáváme chybu, přidáme .catch do řetězce slibů, aby to zvládl:

let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));

// doesn't run: error handled
window.addEventListener('unhandledrejection', event => alert(event.reason));

Pokud ale zapomeneme přidat .catch , poté, co je fronta mikroúloh prázdná, modul spustí událost:

let promise = Promise.reject(new Error("Promise Failed!"));

// Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));

Co když chybu vyřešíme později? Takhle:

let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')), 1000);

// Error: Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));

Nyní, když jej spustíme, uvidíme Promise Failed! nejprve a poté caught .

Pokud bychom o frontě mikroúloh nevěděli, mohli bychom se divit:„Proč unhandledrejection běh psovoda? Chybu jsme zachytili a vyřešili!“

Ale teď už chápeme, že unhandledrejection se generuje, když je fronta mikroúloh dokončena:engine prozkoumá sliby, a pokud je některý z nich ve stavu „odmítnuto“, událost se spustí.

Ve výše uvedeném příkladu .catch přidal setTimeout také spouští. Ale udělá to později, po unhandledrejection již došlo, takže to nic nemění.

Shrnutí

Zpracování příslibů je vždy asynchronní, protože všechny akce příslibů procházejí interní frontou „úloh příslibů“, nazývanou také „fronta mikroúloh“ (výraz V8).

Takže .then/catch/finally handlery jsou vždy volány po dokončení aktuálního kódu.

Pokud potřebujeme zaručit, že se část kódu spustí po .then/catch/finally , můžeme jej přidat do zřetězeného .then zavolejte.

Ve většině Javascript engine, včetně prohlížečů a Node.js, je koncept mikroúloh úzce svázán se „smyčkou událostí“ a „makroúlohami“. Protože tyto nemají přímý vztah ke slibům, jsou popsány v jiné části tutoriálu, v článku Smyčka událostí:mikroúlohy a makroúlohy.