V Promise
je 6 statických metod třída. Zde rychle pokryjeme jejich případy použití.
Promise.all
Řekněme, že chceme, aby se mnoho slibů provedlo paralelně, a počkáme, až budou všechny připraveny.
Například stáhněte několik adres URL paralelně a zpracujte obsah, jakmile budou všechny hotové.
To je to, co Promise.all
je pro.
Syntaxe je:
let promise = Promise.all(iterable);
Promise.all
vezme iterovatelný (obvykle řadu příslibů) a vrátí nový příslib.
Nový příslib se vyřeší, když jsou vyřešeny všechny uvedené přísliby, a pole jejich výsledků se stane jeho výsledkem.
Například Promise.all
níže se po 3 sekundách ustálí a jeho výsledkem je pak pole [1, 2, 3]
:
Promise.all([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member
Vezměte prosím na vědomí, že pořadí výsledných členů pole je stejné jako v jeho zdrojových příslibech. I když vyřešení prvního příslibu trvá nejdéle, je stále na prvním místě v řadě výsledků.
Obvyklým trikem je namapovat řadu pracovních dat do řady příslibů a poté je zabalit do Promise.all
.
Pokud máme například pole adres URL, můžeme je všechny načíst takto:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise of the fetch
let requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
Větší příklad s načítáním uživatelských informací pro pole uživatelů GitHubu podle jejich jmen (mohli bychom načíst pole zboží podle jejich ID, logika je identická):
let names = ['iliakan', 'remy', 'jeresig'];
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));
Promise.all(requests)
.then(responses => {
// all responses are resolved successfully
for(let response of responses) {
alert(`${response.url}: ${response.status}`); // shows 200 for every url
}
return responses;
})
// map array of responses into an array of response.json() to read their content
.then(responses => Promise.all(responses.map(r => r.json())))
// all JSON answers are parsed: "users" is the array of them
.then(users => users.forEach(user => alert(user.name)));
Pokud je některý ze slibů odmítnut, vrátí ho Promise.all
okamžitě odmítne s touto chybou.
Například:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
Zde druhý slib zamítne ve dvou sekundách. To vede k okamžitému zamítnutí Promise.all
, tedy .catch
provede:chyba odmítnutí se stane výsledkem celého Promise.all
.
Pokud je jeden slib odmítnut, Promise.all
okamžitě odmítne a úplně zapomene na ostatní v seznamu. Jejich výsledky jsou ignorovány.
Například pokud existuje více fetch
volání, jako ve výše uvedeném příkladu, a jedno selže, ostatní budou nadále pokračovat, ale Promise.all
už je nebude sledovat. Pravděpodobně se vyrovnají, ale jejich výsledky budou ignorovány.
Promise.all
nedělá nic, aby je zrušilo, protože ve slibech není žádný koncept „zrušení“. V další kapitole se budeme zabývat AbortController
který s tím může pomoci, ale není součástí rozhraní Promise API.
Promise.all(iterable)
umožňuje neslíbené „běžné“ hodnoty v iterable
Normálně Promise.all(...)
přijímá iterovatelné (ve většině případů pole) slibů. Pokud však některý z těchto objektů není příslibem, je předán do výsledného pole „tak, jak je“.
Například zde jsou výsledky [1, 2, 3]
:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000)
}),
2,
3
]).then(alert); // 1, 2, 3
Takže jsme schopni předat hotové hodnoty do Promise.all
kde je to vhodné.
Promise.allSettled
Nedávný přírůstek Toto je nedávný přírůstek do jazyka. Staré prohlížeče mohou vyžadovat polyfilly.
Promise.all
odmítne jako celek, pokud odmítne jakýkoli slib. To je dobré pro případy „vše nebo nic“, kdy potřebujeme vše výsledky úspěšné pokračovat:
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // render method needs results of all fetches
Promise.allSettled
jen čeká na vyřízení všech slibů, bez ohledu na výsledek. Výsledné pole má:
{status:"fulfilled", value:result}
za úspěšné odpovědi,{status:"rejected", reason:error}
za chyby.
Chtěli bychom například načíst informace o více uživatelích. I když jeden požadavek selže, stále nás zajímají ostatní.
Použijme Promise.allSettled
:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://no-such-url'
];
Promise.allSettled(urls.map(url => fetch(url)))
.then(results => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
results
v řádku (*)
výše bude:
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
Takže pro každý příslib dostaneme jeho stav a value/error
.
Polyfill
Pokud prohlížeč nepodporuje Promise.allSettled
, je snadné polyfill:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
V tomto kódu promises.map
převezme vstupní hodnoty a převede je na sliby (pro případ, že by byl splněn neslib) pomocí p => Promise.resolve(p)
a poté přidá .then
handler každému.
Tento handler otočí úspěšný výsledek value
do {status:'fulfilled', value}
a chyba reason
do {status:'rejected', reason}
. To je přesně formát Promise.allSettled
.
Nyní můžeme použít Promise.allSettled
získat výsledky všech dané sliby, i když některé z nich odmítnou.
Promise.race
Podobné jako Promise.all
, ale čeká pouze na první vyrovnaný příslib a dostane jeho výsledek (nebo chybu).
Syntaxe je:
let promise = Promise.race(iterable);
Například zde bude výsledek 1
:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
První příslib zde byl nejrychlejší, takže se stal výsledkem. Poté, co první splněný slib „vyhraje závod“, budou všechny další výsledky/chyby ignorovány.
Promise.any
Podobné jako Promise.race
, ale čeká pouze na první splněný slib a dostane jeho výsledek. Pokud jsou všechny dané přísliby odmítnuty, vrácený příslib je odmítnut s AggregateError
– speciální chybový objekt, který ukládá všechny slibované chyby ve svém errors
vlastnost.
Syntaxe je:
let promise = Promise.any(iterable);
Například zde bude výsledek 1
:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
První slib zde byl nejrychlejší, ale byl zamítnut, takže výsledkem se stal druhý slib. Poté, co první splněný slib „vyhraje závod“, budou všechny další výsledky ignorovány.
Zde je příklad, kdy všechny sliby selžou:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error!
});
Jak můžete vidět, chybové objekty pro neúspěšné sliby jsou k dispozici v errors
vlastnost AggregateError
objekt.
Promise.resolve/reject
Metody Promise.resolve
a Promise.reject
jsou v moderním kódu zřídka potřeba, protože async/await
syntaxe (probereme ji trochu později) je činí poněkud zastaralými.
Uvádíme je zde pro úplnost a pro ty, kteří nemohou používat async/await
z nějakého důvodu.
Promise.resolve
Promise.resolve(value)
vytvoří vyřešený příslib s výsledkem value
.
Stejné jako:
let promise = new Promise(resolve => resolve(value));
Metoda se používá pro kompatibilitu, kdy se očekává, že funkce vrátí slib.
Například loadCached
funkce níže načte URL a zapamatuje si (uloží do mezipaměti) její obsah. Pro budoucí volání se stejnou URL okamžitě získá předchozí obsah z mezipaměti, ale používá Promise.resolve
aby to slíbil, takže vrácená hodnota je vždy slib:
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
Můžeme napsat loadCached(url).then(…)
, protože funkce zaručeně vrátí slib. Vždy můžeme použít .then
po loadCached
. To je účel Promise.resolve
v řádku (*)
.
Promise.reject
Promise.reject(error)
vytvoří odmítnutý příslib s error
.
Stejné jako:
let promise = new Promise((resolve, reject) => reject(error));
V praxi se tato metoda téměř nepoužívá.
Shrnutí
Existuje 6 statických metod Promise
třída:
Promise.all(promises)
– čeká na vyřešení všech slibů a vrátí řadu jejich výsledků. Pokud některý z daných slibů odmítne, stane se chybouPromise.all
a všechny ostatní výsledky jsou ignorovány.Promise.allSettled(promises)
(nedávno přidaná metoda) – čeká na vyřízení všech příslibů a vrátí jejich výsledky jako pole objektů s:status
:"fulfilled"
nebo"rejected"
value
(pokud je splněno) neboreason
(pokud byla zamítnuta).
Promise.race(promises)
– čeká na vyřízení prvního příslibu a jeho výsledek/chyba se stane výsledkem.Promise.any(promises)
(nedávno přidaná metoda) – čeká na splnění prvního slibu a jeho výsledkem se stává výsledek. Pokud jsou všechny dané sliby odmítnuty,AggregateError
se stane chybouPromise.any
.Promise.resolve(value)
– dává vyřešený slib s danou hodnotou.Promise.reject(error)
– provede odmítnutý slib s danou chybou.
Z toho všech Promise.all
je v praxi pravděpodobně nejběžnější.