Hluboký ponor do řešení slibů s objekty včetně tehdejší vlastnosti

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.mjs 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('./file.mjs') 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. 👋