TÅÃda Promise obsahuje Å¡est statických metod. V této kapitole struÄnÄ probereme jejich pÅÃpady použitÃ.
Promise.all
ÅeknÄme, že chceme spustit mnoho pÅÃslibů paralelnÄ a poÄkat, než budou vÅ¡echny pÅipraveny.
NapÅÃklad stáhnout souÄasnÄ obsahy nÄkolika URL, a až budou vÅ¡echny staženy, zpracovat je.
PrávÄ k tomu sloužà Promise.all.
Jeho syntaxe je:
let pÅÃslib = Promise.all(iterovatelnýObjekt);
Promise.all vezme iterovatelný objekt (obvykle pole pÅÃslibů) a vrátà nový pÅÃslib.
Nový pÅÃslib se splnÃ, až budou splnÄny vÅ¡echny pÅÃsliby v seznamu, a jeho výsledkem se stane pole jejich výsledků.
NapÅÃklad následujÃcà Promise.all se usadà za 3 sekundy a jeho výsledkem bude pole [1, 2, 3]:
Promise.all([
new Promise(splÅ => setTimeout(() => splÅ(1), 3000)), // 1
new Promise(splÅ => setTimeout(() => splÅ(2), 2000)), // 2
new Promise(splÅ => setTimeout(() => splÅ(3), 1000)) // 3
]).then(alert); // 1,2,3, až budou pÅÃsliby pÅipraveny: každý pÅÃslib pÅispÄje jednÃm prvkem pole
ProsÃme vÅ¡imnÄte si, že poÅadà prvků výsledného pole je stejné jako poÅadà zdrojových pÅÃslibů. I když prvnÃmu pÅÃslibu trvalo splnÄnà nejdéle, bude v poli výsledků stále prvnÃ.
BÄžným trikem je namapovat pole pracovnÃch dat do pole pÅÃslibů a to pak zabalit do Promise.all.
NapÅÃklad máme-li pole URL, můžeme je vÅ¡echny stáhnout tÃmto způsobem:
let poleURL = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// zmapujeme každý URL na pÅÃslib metody fetch
let požadavky = poleURL.map(url => fetch(url));
// Promise.all poÄká, dokud nebudou vÅ¡echny úkoly vykonány
Promise.all(požadavky)
.then(odpovÄdi => odpovÄdi.forEach(
odpovÄÄ => alert(`${odpovÄÄ.url}: ${odpovÄÄ.status}`)
));
Ve vÄtÅ¡Ãm pÅÃkladu naÄÃtáme informace o uživatelÃch pro pole uživatelů GitHubu podle jejich jmen (zrovna tak můžeme stáhnout pole zbožà podle jejich identifikaÄnÃch ÄÃsel, logika je stejná):
let jména = ['iliakan', 'remy', 'jeresig'];
let požadavky = jména.map(jméno => fetch(`https://api.github.com/users/${jméno}`));
Promise.all(požadavky)
.then(odpovÄdi => {
// vÅ¡echny odpovÄdi se vyhodnotily úspÄÅ¡nÄ
for(let odpovÄÄ of odpovÄdi) {
alert(`${odpovÄÄ.url}: ${odpovÄÄ.status}`); // zobrazà 200 pro každý URL
}
return odpovÄdi;
})
// namapuje pole odpovÄdà do pole výsledků funkcà odpovÄÄ.json(), aby naÄetl jejich obsah
.then(odpovÄdi => Promise.all(odpovÄdi.map(r => r.json())))
// vÅ¡echny odpovÄdi JSON jsou parsovány: âuživateléâ je jejich pole
.then(uživatelé => uživatelé.forEach(uživatel => alert(uživatel.name)));
Bude-li nÄkterý pÅÃslib zamÃtnut, pÅÃslib vrácený metodou Promise.all se okamžitÄ zamÃtne s pÅÃsluÅ¡nou chybou.
NapÅÃklad:
Promise.all([
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(1), 1000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => zamÃtni(new Error("Ouha!")), 2000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(3), 3000))
]).catch(alert); // Error: Ouha!
Zde je druhý pÅÃslib za dvÄ sekundy zamÃtnut. To povede k okamžitému zamÃtnutà Promise.all, takže se spustà .catch: chyba zamÃtnutà se stane výstupem celé metody Promise.all.
Jestliže je jeden pÅÃslib zamÃtnut, Promise.all je okamžitÄ zamÃtnut a ostatnà pÅÃsliby v seznamu jsou okamžitÄ zapomenuty. Jejich výsledky jsou ignorovány.
NapÅÃklad jestliže je zde nÄkolik volánà fetch, podobnÄ jako v uvedeném pÅÃkladu, a jedno z nich selže, ostatnà se budou stále vykonávat dál, ale Promise.all je už nebude sledovat. Tato volánà se pravdÄpodobnÄ usadÃ, ale jejich výsledky budou ignorovány.
Promise.all neudÄlá nic, aby je zruÅ¡il, protože pÅÃsliby nemajà žádný koncept âzruÅ¡enÃâ. V jiné kapitole probereme AbortController, který s tÃm může pomoci, ale ten nenà souÄástà pÅÃslibového API.
Promise.all(iterovatelný) povoluje v objektu iterovatelný i âobyÄejnéâ nepÅÃslibové hodnotyPromise.all(...) bÄžnÄ pÅijÃmá iterovatelný objekt (ve vÄtÅ¡inÄ pÅÃpadů pole) pÅÃslibů. Jestliže vÅ¡ak nÄkterý z tÄchto objektů nenà pÅÃslib, je do výsledného pole pÅedán beze zmÄny.
NapÅÃklad zde jsou výsledky [1, 2, 3]:
Promise.all([
new Promise((splÅ, zamÃtni) => {
setTimeout(() => splÅ(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
Můžeme tedy do Promise.all pÅedat již pÅipravené hodnoty, když se nám to hodÃ.
Promise.allSettled
Jestliže je nÄkterý pÅÃslib zamÃtnut, je zamÃtnut i Promise.all jako celek. To je dobré v pÅÃpadech typu âvÅ¡echno nebo nicâ, kdy potÅebujeme mÃt vÅ¡echny výsledky úspÄÅ¡né, abychom mohli pokraÄovat:
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // metoda render potÅebuje výsledky vÅ¡ech stahovánÃ
Promise.allSettled jednoduÅ¡e poÄká, než se usadà vÅ¡echny pÅÃsliby, bez ohledu na výsledek. Výsledné pole obsahuje:
{status:"fulfilled", value:výsledek}pro úspÄÅ¡né odpovÄdi,{status:"rejected", reason:chyba}pro chyby.
NapÅÃklad bychom rádi naÄetli informace o vÃce uživatelÃch. I kdyby jeden požadavek neuspÄl, ty ostatnà nás stále zajÃmajÃ.
Použijme Promise.allSettled:
let poleURL = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://takovy-url-neni'
];
Promise.allSettled(poleURL.map(url => fetch(url)))
.then(výsledky => { // (*)
výsledky.forEach((výsledek, ÄÃslo) => {
if (výsledek.status == "fulfilled") {
alert(`${poleURL[ÄÃslo]}: ${výsledek.value.status}`);
}
if (výsledek.status == "rejected") {
alert(`${poleURL[ÄÃslo]}: ${výsledek.reason}`);
}
});
});
Pole výsledky na Åádku (*) bude následujÃcÃ:
[
{status: 'fulfilled', value: ...odpovÄÄ...},
{status: 'fulfilled', value: ...odpovÄÄ...},
{status: 'rejected', reason: ...chybový objekt...}
]
Pro každý pÅÃslib tedy zÃskáme jeho stav a value/error.
Polyfill
Jestliže prohlÞeÄ nepodporuje Promise.allSettled, snadno vytvoÅÃme polyfill:
if (!Promise.allSettled) {
const handlerZamÃtnutà = reason => ({ status: 'rejected', reason });
const handlerSplnÄnà = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (pÅÃsliby) {
const pÅevedenéPÅÃsliby = pÅÃsliby.map(p => Promise.resolve(p).then(handlerSplnÄnÃ, handlerZamÃtnutÃ));
return Promise.all(pÅevedenéPÅÃsliby);
};
}
V tomto kódu metoda pÅÃsliby.map vezme vstupnà hodnoty, pÅemÄnà je na pÅÃsliby (jen v pÅÃpadÄ, že byla pÅedána jiná hodnota než pÅÃslib) pomocà p => Promise.resolve(p) a pak ke každému pÅidá handler .then.
Handler pÅemÄnà úspÄÅ¡ný výsledek value na {status:'fulfilled', value} a chybu reason na {status:'rejected', reason}. To je pÅesnÄ formát metody Promise.allSettled.
Nynà můžeme použÃvat Promise.allSettled k zÃskánà výsledků vÅ¡ech zadaných pÅÃslibů, i když byly nÄkteré z nich zamÃtnuty.
Promise.race
Podobá se Promise.all, ale Äeká jen na prvnà pÅÃslib, který se usadÃ, a vezme jeho výsledek (nebo chybu).
Syntaxe je:
let pÅÃslib = Promise.race(iterovatelnýObjekt);
NapÅÃklad zde bude výsledek 1:
Promise.race([
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(1), 1000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => zamÃtni(new Error("Ouha!")), 2000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(3), 3000))
]).then(alert); // 1
Zde byl nejrychlejšà prvnà pÅÃslib, takže výsledkem celé funkce se stane jeho výsledek. Poté, co prvnà usazený pÅÃslib âvyhraje závodâ (âraceâ), budou vÅ¡echny ostatnà výsledky a chyby ignorovány.
Promise.any
Podobá se Promise.race, ale Äeká jen na prvnà pÅÃslib, který bude splnÄn, a vezme jeho výsledek. Budou-li vÅ¡echny zadané pÅÃsliby zamÃtnuty, pak bude vrácený pÅÃslib zamÃtnut s chybou AggregateError â speciálnÃm chybovým objektem, který uložà chyby vÅ¡ech pÅÃslibů do své vlastnosti errors.
Syntaxe je:
let pÅÃslib = Promise.any(iterovatelný);
NapÅÃklad zde bude výsledek 1:
Promise.any([
new Promise((splÅ, zamÃtni) => setTimeout(() => zamÃtni(new Error("Ouha!")), 1000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(1), 2000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => splÅ(3), 3000))
]).then(alert); // 1
Zde byl nejrychlejšà prvnà pÅÃslib, ale ten byl zamÃtnut, takže výsledkem funkce se stane výsledek druhého pÅÃslibu. Poté, co prvnà splnÄný pÅÃslib âvyhraje závodâ, budou vÅ¡echny ostatnà výsledky ignorovány.
Zde je pÅÃklad, v nÄmž vÅ¡echny pÅÃsliby selžou:
Promise.any([
new Promise((splÅ, zamÃtni) => setTimeout(() => zamÃtni(new Error("Au!")), 1000)),
new Promise((splÅ, zamÃtni) => setTimeout(() => zamÃtni(new Error("Chyba!")), 2000))
]).catch(chyba => {
console.log(chyba.constructor.name); // AggregateError
console.log(chyba.errors[0]); // Error: Au!
console.log(chyba.errors[1]); // Error: Chyba!
});
Jak vidÃte, chybové objekty neúspÄÅ¡ných pÅÃslibů jsou k dispozici ve vlastnosti errors objektu AggregateError.
Promise.resolve/reject
Metody Promise.resolve a Promise.reject jsou v modernÃm kódu zapotÅebà jen málokdy, protože syntaxe async/await (probereme ji o nÄco pozdÄji) je Äinà ponÄkud zbyteÄnými.
Probereme je zde pro úplnost a pro ty, kteÅà z nÄjakého důvodu nemohou async/await použÃvat.
Promise.resolve
Promise.resolve(hodnota) vytvoÅà splnÄný pÅÃslib s výsledkem hodnota.
Je to totéž jako:
let pÅÃslib = new Promise(splÅ => splÅ(hodnota));
Tato metoda se použÃvá kvůli kompatibilitÄ, když se od nÄjaké funkce oÄekává, že vrátà pÅÃslib.
NapÅÃklad následujÃcà funkce naÄtiUložené stáhne obsah z URL a zapamatuje si ho (uložà do mezipamÄti). PÅi dalÅ¡Ãch volánÃch se stejným URL okamžitÄ naÄte z mezipamÄti pÅedchozà obsah, ale pomocà Promise.resolve z nÄj vyrobà pÅÃslib, takže návratová hodnota bude vždy pÅÃslib:
let cache = new Map();
function naÄtiUložené(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(odpovÄÄ => odpovÄÄ.text())
.then(text => {
cache.set(url,text);
return text;
});
}
Pak můžeme napsat naÄtiUložené(url).then(â¦), protože tato funkce zaruÄenÄ vrátà pÅÃslib. Za naÄtiUložené tedy můžeme vždy použÃt .then. To je smyslem Promise.resolve na Åádku (*).
Promise.reject
Promise.reject(chyba) vytvoÅà zamÃtnutý pÅÃslib s chybou chyba.
Je to totéž jako:
let pÅÃslib = new Promise((splÅ, zamÃtni) => zamÃtni(chyba));
V praxi se tato metoda témÄÅ nikdy nepoužÃvá.
ShrnutÃ
TÅÃda Promise obsahuje Å¡est statických metod:
Promise.all(pÅÃsliby)â poÄká, až se vÅ¡echny pÅÃsliby splnÃ, a vrátà pole jejich výsledků. Pokud je kterýkoli ze zadaných pÅÃslibů zamÃtnut, jeho chyba se stane chybouPromise.alla vÅ¡echny ostatnà výsledky se ignorujÃ.Promise.allSettled(pÅÃsliby)(nedávno pÅidaná metoda) â poÄká, až se vÅ¡echny pÅÃsliby usadÃ, a vrátà jejich výsledky jako pole objektů obsahujÃcÃch:status:"fulfilled"(splnÄn) nebo"rejected"(zamÃtnut)value(je-li splnÄn) neboreason(je-li zamÃtnut).
Promise.race(pÅÃsliby)â poÄká na prvnà pÅÃslib, který se usadÃ, a výstupem se stane jeho výsledek nebo chyba.Promise.any(pÅÃsliby)(nedávno pÅidaná metoda) â poÄká na prvnà pÅÃslib, který se splnÃ, a výstupem se stane jeho výsledek. Budou-li vÅ¡echny zadané pÅÃsliby zamÃtnuty, chybouPromise.anyse staneAggregateError.Promise.resolve(hodnota)â vytvoÅà splnÄný pÅÃslib se zadanou hodnotou.Promise.reject(chyba)â vytvoÅà zamÃtnutý pÅÃslib se zadanou chybou.
Ze vÅ¡ech tÄchto metod se v praxi asi nejÄastÄji použÃvá Promise.all.
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â¦)