Nie tak dawno opisywałem metodę Future.wait()
języka Dart (wpis znajduje się tutaj), dzięki której możemy zoptymalizować działania asynchroniczne. Oczywiście JavaScript niczym nie ustępuje Dartowi i aplikacje pisane w React Native również mogą wykorzystywać tego rodzaju funkcję, która zwiększy prędkość działania aplikacji z kilkoma metodami, które potrzebują czasu na ich wykonanie.
Opiszę tutaj dokładnie ten sam przykład, co poprzednim razem. Zacznijmy więc od przykładu pokazującego nieoptymalne pobieranie danych z API, gdzie żądania wykonywane są jedno po drugim:
let responseJavaScript =
await fetch('https://www.googleapis.com/books/v1/volumes?q={javascript}');
let responseReactNative =
await fetch('https://www.googleapis.com/books/v1/volumes?q={reactnative}');
let responseAndroid =
await fetch('https://www.googleapis.com/books/v1/volumes?q={android}');
let responseIos =
await fetch('https://www.googleapis.com/books/v1/volumes?q={ios}');
Widzimy tutaj kolejne żądania API odnośnie książek, każde dotyczące innego hasła. To, co powoduje, że powyższy kod nie jest optymalny, wynika z tego, że za każdym razem, aby wykonać kolejne żądanie, musimy odczekać aż poprzednie się wykona, chociaż w rzeczywistości nie potrzebujemy znać wcześniejszej odpowiedzi, aby wykonać kolejne odpytanie API.
Przyjmijmy w tym przypadku, że:
- pierwsze żądanie wykona się w czasie 200 ms
- drugie żądanie wykona się w czasie 150 ms
- trzecie żądanie wykona się w czasie 300 ms
- czwarte żądanie wykona się w czasie 250 ms
W tym przykładzie, aby obliczyć czas potrzebny na wykonanie tych wszystkich żądań, wystarczy wszystkie te czasy ze sobą zsumować. Znaczy to, że zostaną one wykonane w czasie 900 ms.
Więc jak to zoptymalizować? Z pomocą tutaj przychodzi funkcja Promise.all()
, której zadaniem jest na poczekanie na wykonanie się wszystkich zadań asynchronicznych, oraz zwrócenie listy zawierającej wyniki tych działań. Spróbujmy zobaczyć powyższy przykład napisany z wykorzystaniem Promise.all()
:
let responses = await Promise.all([
fetch('https://www.googleapis.com/books/v1/volumes?q={javascript}'),
fetch('https://www.googleapis.com/books/v1/volumes?q={reactnative}'),
fetch('https://www.googleapis.com/books/v1/volumes?q={android}'),
fetch('https://www.googleapis.com/books/v1/volumes?q={ios}')
]);
let responseJavaScript = responses[0];
let responseReactNative = responses[1];
let responseAndroid = responses[2];
let responseIos = responses[3];
Biorąc pod uwagę wyżej wymienione czasy wykonywania się każdego z żądań, wykorzystanie Promise.all()
sprawi, że wszystkie te żądania wykonają się w ciągu 300 ms. Ten czas tak naprawdę wynika z tego, że dokładnie tyle wynosi najdłuższy czas każdego z tych żądań.
Promise.all()
przyjął listę zadań asynchronicznych, które zostały uruchomione w jednym momencie i oczekuje, aż wszystkie z nich zakończą się sukcesem. W tym przypadku wynikiem jest lista odpowiedzi w kolejności takiej, w jakiej żądania zostały przekazane, a nie w kolejności od najszybciej wykonanego.
Wiadome jest to, że w przypadku wykonywania każdego z tych działań może pojawić się błąd. W przypadku wystąpienia pierwszego błędu pozostałe zadania są anulowane oraz zostaje rzucony wyjątek.
Jednak co zrobić w przypadku, kiedy chcemy znać odpowiedzi żądań, które zakończyły się powodzeniem, a tylko ominąć te, w przypadku których pojawił się błąd? To również jest bardzo łatwe zadanie i wystarczy złapać wyjątek w wykonywanych zadaniach:
let responses = await Promise.all([
fetch('https://www.googleapis.com/books/v1/volumes?q={javascript}')
.catch(() => { }),
fetch('https://www.googleapis.com/books/v1/volumes?q={reactnative}')
.catch(() => { }),
fetch('https://www.googleapis.com/books/v1/volumes?q={android}')
.catch(() => { }),
fetch('https://www.googleapis.com/books/v1/volumes?q={ios}')
.catch(() => { })
]);
let responseJavaScript = responses[0];
let responseReactNative = responses[1];
let responseAndroid = responses[2];
let responseIos = responses[3];
Jeśli w powyższym przykładzie, żądanie do API o wyniki dotyczące systemu iOS spowoduje wystąpienie błędu, zmienna responseIos
będzie miała wartość undefined
, a te, które zostały wykonane prawidłowo, będą posiadały prawidłowe odpowiedzi.
Jak widać, przy pomocy Promise.all()
możemy w znacznym stopniu przyspieszyć wykonywanie działań asynchronicznych, sprawiając, że zostaną one wystartowane w jednej chwili, a czas wykonywania wszystkich zadań, będzie dokładnie równy czasowi najdłuższego z tych zadań. Ta sztuczka zadziała jednak tylko w przypadku, gdy wynik wcześniejszego żądania nie jest przekazywany do któregoś z kolejnych.