Promises – metody

Opublikowane przez lewy w dniu

Tym razem ciąg dalszy o Promisach. W pierwszym wpisie były podstawy, dziś przedstawię metody Promise: Promise.all, Promise.race oraz nowsze, takie jak Promise.allSettled i Promise.any. Na koniec będzie też trochę o async i await.

Promise.all

Promise.all to metoda za pomocą której możemy poczekać na wykonanie wszystkich interesujących nas obietnic. Jako argument przyjmuje tablicę tychże Promise i jako wynik zwraca również tablicę z wynikami zwróconymi przez obietnice.

Promise.all([
    randPromiseResolve(),
    randPromiseResolve(),
    randPromiseResolve(),
])
    .then((result) => log(result));

randPromiseResolve jest funkcją zwracającą Promise który zostanie rozwiązany poprawnie. Tak napisany kod poczeka aż wszystkie Promise podane w tablicy zostaną wykonane (zwrócą wynik) i dopiero wtedy wykonany zostanie kod z metody then. W powyższym przypadku jest to zwykły console.log wyników.

log [ { result: 0, time: 180 },
  { result: 1, time: 397 },
  { result: 2, time: 718 } ]

Zakładam tutaj że Promise wykonają się poprawnie. Co w sytuacji kiedy jedna z obietnic nie wykona się poprawnie. Powyższy kod zwróci informację o nieobsłużonym błędzie UnhandledPromiseRejectionWarning. Aby obsłużyć błąd z Promise muszę dodać blok catch do kodu.

Promise.all([
    randPromiseResolve(),
    randPromiseResolve(),
    randPromiseResolve(),
    Promise.reject('error all'),
])
    .then((result) => log(result))
    .catch((error) => logError(error));

Wynik działanie tego kodu to console.error logError error all. Na co należy zwrócić uwagę przy korzystaniu z Promise.all to fakt, że wystarczy aby jeden z Promise zakończył się błędem a wykona się kod z bloku catch. Natomiast kod z bloku then zostanie pominięty nawet dla Promise które wykonały się poprawnie.

Promise.race

Kolejna metoda z obiektu Promise to Promise.race. Metoda ta podobnie jak poprzednik jako argument przyjmuje tablicę Promise jednak wynikiem jest wynik z Promise który został rozwiązany najszybciej.

Promise.race([
    randPromiseResolve(),
    randPromiseResolve(),
    randPromiseResolve(),
])
    .then((result) => log(result));

Po uruchomieniu tego kodu jako wynik otrzymam np. log { result: 1, time: 242 } czyli wynik z Promise który wykonał się najszybciej. Podobnie jak w przypadku Promise.all jeżeli któryś z powyższych Promise zakończył by się niepowodzeniem otrzymałbym komunikat o nieobsłużonym błędzie. Aby temu zapobiec ponownie mogę wykorzystać blok catch.

Promise.race([
    randPromiseResolve(),
    randPromiseResolve(),
    randPromiseResolve(),
    new Promise(((resolve, reject) => setTimeout(reject, 1000, 'error race'))),
])
    .then((result) => log(result))
    .catch((error) => logError(error));

Teraz jeżeli Promise który wykona się najszybciej zwróci błąd to zostanie on obsłużony przez blok catch. Różnica w stosunku do Promise.all jest taka, że jeżeli najszybciej wykona się poprawny Promise to jego wynik zostanie obsłużony przez then nawet jeżeli jakiś inny Promise zakończy się błędem.

Promise.allSettled

Kolejną metodą o której chcę opowiedzieć jest Promise.allSettled. Za pomocą której mogę obsłużyć zakończenie wszystkich Promise bez względu na to czy wykonały się poprawnie czy też nie. Podobnie jak poprzednie metody jako argument przyjmuje ona tablicę Promise. Jako wynik również zwraca tablicę z wynikami tych obietnic. Różnica w stosunku do Promise.all jest taka, że nawet jeżeli któryś z Promise zwróci błąd to i tak obsłużyć wywołanie będę mógł za pomocą bloku then.

Promise.allSettled([
    randPromiseResolve(),
    randPromiseResolve(),
    randPromiseResolve(),
    Promise.reject('error all'),
])
    .then((result) => log(result));

Po uruchomieniu tego kodu jako wynik otrzymam:

log [
  { status: 'fulfilled', value: { result: 0, time: 638 } },
  { status: 'fulfilled', value: { result: 1, time: 604 } },
  { status: 'fulfilled', value: { result: 2, time: 483 } },
  { status: 'rejected', reason: 'error all' }
]

Jak widać wynikiem jest tablica obiektów które zawierają pole status i jeżeli Promise wykonał się poprawnie fulfilled to zwracane jest pole value z wynikiem tego Promise. Jeżeli Promise został błędnie wykonany rejected to dostępne jest pole reason z informacją dlaczego Promise został odrzucony.

Promise.allSettled jest metodą nowszą niż opisane wcześniej i dostępny jest w node od wersji 12.9, Chrome wersji 76, a Firefox 71, kompatybilność można sprawdzić tu.

Promise.any

Promise.any to jeszcze nowsza metoda (node od wersji 15, Chromie 85, Firefox 79, kompatybilność) i działaniem zbliżona do Promise.race. Jako argument przyjmuje tablice Promise a jako wynik zwraca rezultat z pierwszego Promise który wykonał się poprawnie.

Promise.any([
    randPromiseRejected(),
    randPromiseResolve(),
])
    .then((result) => log(result));

Przykładowy wynik to log { result: 1, time: 782 }. Od Promise.race różni się tym, że ignoruje błąd zwrócony przez Promise o ile są jeszcze Promise które się nie zakończyły. Dopiero jeżeli wszystkie Promise zakończą się błędem to dostaniemy błąd, który możemy obsłużyć w bloku catch.

Promise.any([
    randPromiseRejected(),
    randPromiseRejected(),
])
    .then((result) => log(result))
    .catch((error) => logError(error));

Po uruchomienie tego kodu jako wynik otrzymam: logError [AggregateError: All promises were rejected].

Async/await

Ostatnim zagadnieniem które chce przedstawić są konstrukcje async i await. Są to instrukcje których możemy używać z promisami w celu poprawy czytelności kodu. Za pomocą instrukcji await mogę „wymusić” aby skrypt czekał z wykonaniem dalszych instrukcji na zakończenie wykonania asynchronicznej operacji. Instrukcji await mogę użyć tylko i wyłącznie wewnątrz funkcji asynchronicznej. Aby oznaczyć funkcję jako asynchroniczną używam słówka async przed deklaracją funkcji. Wynik działania funkcji asynchronicznej mogę w prosty sposób przypisać do zmiennej const result = await randPromiseResolve(). Przykładowy kod:

async function getResult() {
    console.log('getResult');
    const result = await randPromiseResolve();
    log(result);
}

getResult();

Przykładowy wynik może wyglądać następująco:

getResult
log { result: 0, time: 739 }

Linia 4 wykonana się dopiero wtedy jak funkcja randPromiseResolve zwróci wynik, do tego czasu dalsze wykonanie instrukcji z funkcji asynchronicznej będzie „wstrzymane”.

Obsługę błędów zwracanych przez Promise mogę obsłużyć za pomocą bloków try { ... } catch() { ... }. Przykładowy kod:

async function getResult() {
    log('getResult');
    try {
        const result = await randPromiseRejected();
        log(result);
    } catch(error) {
        logError(error);
    }
}

getResult();

Tak napisany kod jeżeli napotka Promise który nie zostanie poprawnie wykonany to pomija pozostałe instrukcje z bloku try i wykona kod zawarty w bloku catch. Przykładowy wynik działa powyższego kodu:

log getResult
logError { result: 0, time: 308 }

Jeżeli po wywołaniu funkcji getResult() występowałyby inne instrukcje to oczywiście nie byłyby one zblokowane przez await ponieważ nie są one częścią funkcji asynchronicznej.

async function getResult() {
    console.log('getResult');
    const result = await randPromiseResolve();
    log(result);
}

getResult();
log('test');

Po uruchomienie kodu otrzymam taki rezultat:

getResult
log test
log { result: 0, time: 565 }

Jeżeli chciałbym aby kod występujący po getResult() wykonał się faktycznie po tej funkcji to muszę umieścić ten kod wewnątrz funkcji asynchronicznej. W celu wykonania funkcji po uruchomieniu skryptu mogę użyć IIFE.

async function getResult() {
    log('getResult');
    try {
        const result = await randPromiseRejected();
        log(result);
    } catch(error) {
        logError(error);
    }
}

(async function() {
    await getResult();
    log('after getResult()');
})(); //IIFE

I po uruchomieniu powinniście otrzymać rezultat zbliżony do tego:

log getResult
logError { result: 0, time: 966 }
log after getResult()

Kod i film

To tyle jeżeli chodzi o Promise w JavaScript.

Kod który wykorzystałem w tym wpisie dostępny jest na githubie.

Film z zagadnieniami z wpisu możesz znaleźć tutaj: https://youtu.be/FPLrMSM0UcI


0 Komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *