Slib

„Slibování“ je dlouhé slovo pro jednoduchou transformaci. Je to převod funkce, která přijímá zpětné volání, na funkci, která vrací slib.

Takové transformace jsou často vyžadovány v reálném životě, protože mnoho funkcí a knihoven je založeno na zpětném volání. Ale sliby jsou pohodlnější, takže má smysl je slíbit.

Pro lepší pochopení si ukažme příklad.

Například máme loadScript(src, callback) z kapitoly Úvod:zpětná volání.

function loadScript(src, callback) {
 let script = document.createElement('script');
 script.src = src;

 script.onload = () => callback(null, script);
 script.onerror = () => callback(new Error(`Script load error for ${src}`));

 document.head.append(script);
}

// usage:
// loadScript('path/script.js', (err, script) => {...})

Funkce načte skript s daným src a poté zavolá callback(err) v případě chyby nebo callback(null, script) v případě úspěšného načtení. To je rozšířená dohoda o používání zpětných volání, viděli jsme ji již dříve.

Pojďme si to slíbit.

Vytvoříme novou funkci loadScriptPromise(src) , který udělá totéž (načte skript), ale místo použití zpětných volání vrátí příslib.

Jinými slovy, předáme to pouze src (ne callback ) a dostanete na oplátku slib, který se vyřeší pomocí script když je načtení úspěšné, a v opačném případě odmítne s chybou.

Tady to je:

let loadScriptPromise = function(src) {
 return new Promise((resolve, reject) => {
 loadScript(src, (err, script) => {
 if (err) reject(err);
 else resolve(script);
 });
 });
};

// usage:
// loadScriptPromise('path/script.js').then(...)

Jak vidíme, nová funkce je obalem původního loadScript funkce. Říká tomu poskytování vlastního zpětného volání, které v překladu znamená slib resolve/reject .

Nyní loadScriptPromise dobře zapadá do kódu založeného na slibech. Pokud se nám sliby líbí víc než zpětná volání (a brzy pro to uvidíme více důvodů), použijeme je místo toho.

V praxi můžeme potřebovat slíbit více než jednu funkci, takže má smysl použít pomocníka.

Budeme to nazývat promisify(f) :přijímá funkci slíbení f a vrátí funkci wrapper.

function promisify(f) {
 return function (...args) { // return a wrapper-function (*)
 return new Promise((resolve, reject) => {
 function callback(err, result) { // our custom callback for f (**)
 if (err) {
 reject(err);
 } else {
 resolve(result);
 }
 }

 args.push(callback); // append our custom callback to the end of f arguments

 f.call(this, ...args); // call the original function
 });
 };
}

// usage:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);

Kód může vypadat trochu složitě, ale je v podstatě stejný, jaký jsme napsali výše, a zároveň slibuje loadScript funkce.

Volání na číslo promisify(f) vrátí obálku kolem f (*) . Tento obal vrátí příslib a přesměruje volání na původní f , sledující výsledek ve vlastním zpětném volání (**) .

Zde promisify předpokládá, že původní funkce očekává zpětné volání s přesně dvěma argumenty (err, result) . S tím se setkáváme nejčastěji. Pak je naše vlastní zpětné volání ve správném formátu a promisify v takovém případě funguje skvěle.

Ale co když původní f očekává zpětné volání s více argumenty callback(err, res1, res2, ...) ?

Svého pomocníka můžeme vylepšit. Pojďme vytvořit pokročilejší verzi promisify .

  • Při volání jako promisify(f) mělo by to fungovat podobně jako výše uvedená verze.
  • Při volání jako promisify(f, true) , měl by vrátit příslib, který se vyřeší s polem výsledků zpětného volání. To je přesně pro zpětná volání s mnoha argumenty.
// promisify(f, true) to get array of results
function promisify(f, manyArgs = false) {
 return function (...args) {
 return new Promise((resolve, reject) => {
 function callback(err, ...results) { // our custom callback for f
 if (err) {
 reject(err);
 } else {
 // resolve with all callback results if manyArgs is specified
 resolve(manyArgs ? results : results[0]);
 }
 }

 args.push(callback);

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

// usage:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);

Jak můžete vidět, je to v podstatě stejné jako výše, ale resolve se volá pouze s jedním nebo se všemi argumenty v závislosti na tom, zda manyArgs je pravdivý.

Pro exotičtější formáty zpětného volání, jako jsou ty bez err vůbec:callback(result) , můžeme takové funkce slíbit ručně bez použití pomocníka.

Existují také moduly s trochu flexibilnějšími funkcemi promisifikace, např. es6-slibovat. V Node.js je vestavěný util.promisify funkce pro to.

Poznámka:

Promisification je skvělý přístup, zvláště když používáte async/await (viz další kapitola), ale není to úplná náhrada za zpětná volání.

Pamatujte, že příslib může mít pouze jeden výsledek, ale zpětné volání může být technicky vzato mnohokrát.

Slibování je tedy určeno pouze pro funkce, které volají zpětné volání jednou. Další hovory budou ignorovány.