â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 Ãvod: callbacky.
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 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.
KomentáÅe
<code>, pro nÄkolik Åádků je obalte znaÄkou<pre>, pro vÃce než 10 Åádků vložte odkaz na pÃskoviÅ¡tÄ (plnkr, jsbin, codepenâ¦)