Skip to content

Latest commit

 

History

History
132 lines (94 loc) · 5.81 KB

File metadata and controls

132 lines (94 loc) · 5.81 KB

Promisifikace

„Promisifikace“ je dlouhý výraz pro jednoduchou transformaci. Je to převod funkce, která přijímá callback, na funkci, která vrací příslib.

Takové transformace jsou ve skutečném životě často požadovány, jelikož mnoho funkcí a knihoven je založeno na callbaccích. Přísliby jsou však vhodnější, takže promisifikace těchto funkcí dává smysl.

Pro lepší porozumění se podívejme na příklad.

Máme například funkci načtiSkript(zdroj, callback) z kapitoly info:callbacks.

function načtiSkript(zdroj, callback) {
  let skript = document.createElement('script');
  skript.src = zdroj;

  skript.onload = () => callback(null, skript);
  skript.onerror = () => callback(new Error(`Chyba načítání skriptu pro ${zdroj}`));

  document.head.append(skript);
}

// použití:
// načtiSkript('path/script.js', (chyba, skript) => {...})

Tato funkce načte skript ze zadaného zdroje zdroj a pak volá callback(chyba) v případě chyby nebo callback(null, skript) v případě úspěšného načtení. To je široce rozšířená úmluva pro používání callbacků, kterou jsme již viděli.

Promisifikujme tuto funkci.

Vytvořme novou funkci načtiSkriptPříslibem(zdroj), která udělá totéž (načte skript), ale místo používání callbacků vrátí příslib.

Jinými slovy, předáme do ní jenom zdroj (ne callback) a jako návratovou hodnotu obdržíme příslib, který se splní s hodnotou skript, bude-li načítání úspěšné, a jinak se zamítne s chybou.

Zde je:

let načtiSkriptPříslibem = function(zdroj) {
  return new Promise((splň, zamítni) => {
    načtiSkript(zdroj, (chyba, skript) => {
      if (chyba) zamítni(chyba);
      else splň(skript);
    });
  });
};

// použití:
// načtiSkriptPříslibem('path/script.js').then(...)

Jak vidíme, nová funkce je obalem okolo původní funkce načtiSkript. Zavolá ji a poskytne jí svůj vlastní callback, který se převede na příslib splň/zamítni.

Nyní načtiSkriptPříslibem dobře zapadne do kódu založeného na příslibech. Máme-li raději přísliby než callbacky (a brzy pro to uvidíme další důvody), použijeme místo původní funkce tuto.

V praxi můžeme potřebovat promisifikovat více než jednu funkci, takže dává smysl použít pomocnou funkci.

Nazveme ji promisifikuj(f): bude přijímat funkci f, která má být promisifikována, a vrátí obalovou funkci.

function promisifikuj(f) {
  return function (...argumenty) { // vrátí obalovou funkci (*)
    return new Promise((splň, zamítni) => {
      function callback(chyba, výsledek) { // náš vlastní callback pro f (**)
        if (chyba) {
          zamítni(chyba);
        } else {
          splň(výsledek);
        }
      }

      argumenty.push(callback); // připojí náš vlastní callback na konec argumentů funkce f

      f.call(this, ...argumenty); // zavolá původní funkci
    });
  };
}

// použití:
let načtiSkriptPříslibem = promisifikuj(načtiSkript);
načtiSkriptPříslibem(...).then(...);

Kód může vypadat trochu složitě, ale v zásadě je to totéž, co jsme napsali výše, když jsme promisifikovali funkci načtiSkript.

Volání promisifikuj(f) vrátí obal okolo f (*). Tento obal vrátí příslib, předá volání původní funkci f a výsledek zpracuje ve vlastním callbacku (**).

Zde funkce promisifikuj předpokládá, že původní funkce očekává callback s právě dvěma argumenty (chyba, výsledek). S tím se setkáváme nejčastěji. Náš vlastní callback je přesně ve správném formátu a funkce promisifikuj v takovém případě funguje skvěle.

Ale co když původní funkce f očekává callback s více argumenty callback(chyba, výsledek1, výsledek2, ...)?

Můžeme naši pomocnou funkci vylepšit. Vytvořme pokročilejší verzi promisifikuj.

  • Když bude volána jako promisifikuj(f), měla by fungovat podobně jako uvedená verze.
  • Když bude volána jako promisifikuj(f, true), měla by vrátit příslib, který se splní s polem výsledků callbacku. To je určeno právě pro callbacky s mnoha argumenty.
// promisifikuj(f, true) pro získání pole výsledků
function promisifikuj(f, víceArgumentů = false) {
  return function (...argumenty) {
    return new Promise((splň, zamítni) => {
      function *!*callback(chyba, ...výsledky*/!*) { // náš vlastní callback pro f
        if (chyba) {
          zamítni(chyba);
        } else {
          // je-li uvedeno víceArgumentů, splní se se všemi výsledky callbacku
          *!*splň(víceArgumentů ? výsledky : výsledky[0]);*/!*
        }
      }

      argumenty.push(callback);

      f.call(this, ...argumenty);
    });
  };
}

// použití:
f = promisifikuj(f, true);
f(...).then(poleVýsledků => ..., chyba => ...);

Jak vidíte, je to v zásadě totéž jako výše, ale splň se volá buď jen s jedním, nebo se všemi argumenty, podle toho, zda je argument víceArgumentů pravdivý.

Pro exotičtější formáty callbacků, např. takové, které vůbec neobsahují argument chyba: callback(výsledek), můžeme takové funkce promisifikovat ručně bez použití pomocné funkce.

Existují i moduly s trochu flexibilnějšími promisifikačními funkcemi, např. es6-promisify. V Node.js k tomu slouží zabudovaná funkce util.promisify.

Promisifikace je skvělý přístup, zvláště když používáte `async/await` (bude vysvětleno později v kapitole <info:async-await>), ale není to úplná náhrada za callbacky.

Pamatujte, že příslib může mít jen jediný výsledek, ale callback lze technicky volat mnohokrát.

Promisifikace má tedy smysl jen u funkcí, které volají callback pouze jednou. Další volání budou ignorována.