tl;dr
Když vyřešíte příslib pomocí objektu, který definuje then
probíhá metoda „standardního slibného chování“. then
metoda bude provedena s resolve
a reject
argumenty okamžitě. Volání then
s jinými hodnotami přepíše počáteční hodnotu rozlišení slibu. Toto chování umožňuje rekurzivní řetězce slibů.
Přiměřeně nový import
metoda načítání modulů JavaScriptu není výjimkou.
Nedávno mě zaujaly dva tweety týkající se slibů a dynamických importů. Strávil jsem dvě hodiny čtením specifikace a tento příspěvek sdílí můj myšlenkový proces a to, co jsem se naučil o slibech a řetězcích slibů.
Tweet 1:Způsob, jak "tak trochu" společně hacknout, čeká na nejvyšší úroveň
Surma sdílel „hack, díky kterému budou špičkoví čekat na práci“.
Můžete zahrnout vložený skript type="module"
ve vašem HTML, který dynamicky importuje další modul.
<script type="module">
import('./file.mjs');
</script>
Samotný modul exportuje then
funkce, která bude provedena okamžitě, aniž by ji cokoli volalo.
// file.mjs
export async function then() {
// yay!!! I can use async/await here
// also yay!!! this function will be executed automatically
}
Toto chování můžete použít k definování file
jako vstupní bod vaší aplikace a použijte async/await right wait v then
funkce.
Důležitý detail:then
funkce se provede automaticky.
Tweet 2:Blokovací chování dynamických importů
Johannes Ewald sdílel, že dynamické importy mohou „zablokovat“ spuštění kódu, pokud vrácená hodnota importu obsahuje then
funkce.
// file.mjs
export function then() {}
// index.mjs
async function start() {
const a = await import('./file.mjs');
// the following lines will never be executed
console.log(a);
}
Výše uvedené úryvky nikdy nic nezaprotokolují.
Upraveno:Jak zdůraznil Mathias Bynens – výše uvedený úryvek je součástí návrhu na čekání na nejvyšší úrovni.
Důležitý detail:import('
nikdy nevyřeší.
Proces řešení příslibů
Chování, které jste viděli ve výše uvedených příkladech, nesouvisí s import
spec (problém GitHubu popisuje toto chování velmi podrobně). Základem je spíše specifikace ECMAscript popisující proces řešení slibů.
8. If Type(resolution) is not Object, then
a. Return FulfillPromise(promise, resolution).
9. Let then be Get(resolution, "then").
10. If then is an abrupt completion, then
a. Return RejectPromise(promise, then.[[Value]]).
11. Let thenAction be then.[[Value]].
12. If IsCallable(thenAction) is false, then
a. Return FulfillPromise(promise, resolution).
13. Perform EnqueueJob(
"PromiseJobs", PromiseResolveThenableJob, « promise, resolution, thenAction »
).
Pojďme si projít možnosti řešení slibu krok za krokem.
Slib se řeší čímkoli jiným než objektem
Pokud vyřešíte příslib pomocí řetězcové hodnoty (nebo čehokoli, co není objekt), tato hodnota bude rozlišením příslibu.
Promise.resolve('Hello').then(
value => console.log(`Resolution with: ${value}`)
);
// log: Resolution with: Hello
Slib se vyřeší pomocí objektu včetně then
což je abruptCompletion
Pokud vyřešíte příslib pomocí objektu obsahujícího then
vlastnost, jejíž přístup má za následek výjimku, vede k odmítnutí příslibu.
const value = {};
Object.defineProperty(
value,
'then',
{ get() { throw new Error('no then!'); } }
);
Promise.resolve(value).catch(
e => console.log(`Error: ${e}`)
);
// log: Error: no then!
Slib vyřeší objekt včetně then
což není funkce
Pokud vyřešíte příslib pomocí objektu obsahujícího then
vlastnost, která není funkcí, je příslib vyřešen se samotným objektem.
Promise.resolve(
{ then: 42 }
).then(
value => console.log(`Resolution with: ${JSON.stringify(value)}`)
);
// log: Resolution with: {"then":42}
Slib se vyřeší s objektem včetně then
což je funkce
Nyní se dostáváme k vzrušující části, která je základem rekurzivních slibových řetězců. Začal jsem jít do králičí nory, abych popsal kompletní funkcionalitu, ale zahrnovalo by to odkazy na několik dalších částí specifikace ECMAScript. Zacházení do podrobností by bylo mimo rozsah tohoto příspěvku.
Kritická část tohoto posledního kroku spočívá v tom, že když se příslib vyřeší pomocí objektu, který obsahuje then
metodu, kterou proces rozlišení zavolá then
s obvyklými argumenty slibu resolve
a reject
pro vyhodnocení konečné hodnoty rozlišení. Pokud resolve
se nenazývá slib nebude vyřešen.
Promise.resolve(
{ then: (...args) => console.log(args) }
).then(value => console.log(`Resolution with: ${value}`));
// log: [fn, fn]
// | \--- reject
// resolve
// !!! No log of a resolution value
Toto definované chování vede k navždy čekajícímu příslibu druhého příkladu Tweetu. resolve
není volána, a proto se slib nikdy nevyřeší.
Promise.resolve(
{
then: (resolve) => {
console.log('Hello from then');
resolve(42);
}
}
).then(value => console.log(`Resolution with: ${value}`));
// log: Hello from then
// log: Resolution with: 42
Všechno to souvisí
Naštěstí chování sdílené na Twitteru mi nyní dává smysl. Navíc je to popsané chování, které používáte k rekurzivnímu řetězení slibů každý den.
(async () => {
const value = await new Promise((resolve, reject) => {
// the outer promise will be resolved with
// an object including a `then` method
// (another promise)
// and the resolution of the inner promise
// becomes the resolution of the outer promise
return resolve(Promise.resolve(42));
});
console.log(`Resolution with: ${value}`);
})();
// log: Resolution with: 42
Překvapivý okrajový případ
Při používání then
musíte být velmi opatrní -hack, může nastat případ, kdy proces řešení povede k neočekávanému chování.
Promise.resolve({
then: resolve => resolve(42),
foo: 'bar'
}).then(value => console.log(`Resolution with: ${value}`));
// log: Resolution with: 42
I když se výše uvedený slib řeší objektem obsahujícím několik vlastností, vše, co získáte, je 42
.
Dynamický import není výjimkou a řídí se standardním procesem řešení slibů
Když použijete dynamický import
funkce pro načtení modulů JavaScript, import
následuje stejný proces, protože vrací slib. Hodnota rozlišení importovaného modulu bude objekt obsahující všechny exportované hodnoty a metody.
Pro případ, že exportujete then
funkce se spustí zpracování specifikovaného slibu, aby se vyhodnotilo, jaké by mělo být celkové rozlišení. then
funkce může přepsat vše ostatní, co by mohlo být součástí tohoto modulu.
// file.mjs
export function then (resolve) {
resolve('Not what you expect!');
}
export function getValue () {
return 42;
}
// index.mjs
import('./file.mjs').then(
resolvedModule => console.log(resolvedModule)
);
// log: Not what you expect
Rozhodně se vyhnu pojmenování svých funkcí then
. Nalezení takové chyby může trvat několik minut. 🙈
A to je pro dnešek vše! Doufám, že to bylo užitečné a brzy si promluvíme. 👋