Promise API

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 .

V případě chyby jsou ostatní přísliby ignorovány

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:

  1. 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 chybou Promise.all a všechny ostatní výsledky jsou ignorovány.
  2. 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) nebo reason (pokud byla zamítnuta).
  3. Promise.race(promises) – čeká na vyřízení prvního příslibu a jeho výsledek/chyba se stane výsledkem.
  4. 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 chybou Promise.any .
  5. Promise.resolve(value) – dává vyřešený slib s danou hodnotou.
  6. Promise.reject(error) – provede odmítnutý slib s danou chybou.

Z toho všech Promise.all je v praxi pravděpodobně nejběžnější.


No