Poznaj p臋tl臋 zdarze艅 JavaScript, jej rol臋 w programowaniu asynchronicznym i jak umo偶liwia wydajne, nieblokuj膮ce wykonywanie kodu w r贸偶nych 艣rodowiskach.
Demistyfikacja p臋tli zdarze艅 JavaScript: Zrozumienie przetwarzania asynchronicznego
JavaScript, znany ze swojej jednow膮tkowej natury, mo偶e skutecznie obs艂ugiwa膰 wsp贸艂bie偶no艣膰 dzi臋ki p臋tli zdarze艅 (Event Loop). Mechanizm ten jest kluczowy do zrozumienia, w jaki spos贸b JavaScript zarz膮dza operacjami asynchronicznymi, zapewniaj膮c responsywno艣膰 i zapobiegaj膮c blokowaniu zar贸wno w 艣rodowiskach przegl膮darkowych, jak i Node.js.
Czym jest p臋tla zdarze艅 JavaScript?
P臋tla zdarze艅 to model wsp贸艂bie偶no艣ci, kt贸ry pozwala JavaScriptowi wykonywa膰 operacje nieblokuj膮ce, mimo 偶e jest jednow膮tkowy. Ci膮gle monitoruje stos wywo艂a艅 (Call Stack) i kolejk臋 zada艅 (Task Queue, znana r贸wnie偶 jako Callback Queue) i przenosi zadania z kolejki zada艅 na stos wywo艂a艅 w celu ich wykonania. Tworzy to iluzj臋 r贸wnoleg艂ego przetwarzania, poniewa偶 JavaScript mo偶e inicjowa膰 wiele operacji, nie czekaj膮c na zako艅czenie ka偶dej z nich przed rozpocz臋ciem nast臋pnej.
Kluczowe komponenty:
- Stos wywo艂a艅 (Call Stack): Struktura danych typu LIFO (Last-In, First-Out), kt贸ra 艣ledzi wykonywanie funkcji w JavaScript. Gdy funkcja jest wywo艂ywana, jest umieszczana na stosie. Gdy funkcja zako艅czy dzia艂anie, jest z niego zdejmowana.
- Kolejka zada艅 (Task Queue / Callback Queue): Kolejka funkcji zwrotnych (callback) oczekuj膮cych na wykonanie. Te funkcje zwrotne s膮 zazwyczaj zwi膮zane z operacjami asynchronicznymi, takimi jak timery, 偶膮dania sieciowe i zdarzenia u偶ytkownika.
- Web API (lub API Node.js): S膮 to interfejsy API dostarczane przez przegl膮dark臋 (w przypadku JavaScript po stronie klienta) lub Node.js (dla JavaScript po stronie serwera), kt贸re obs艂uguj膮 operacje asynchroniczne. Przyk艂ady obejmuj膮
setTimeout,XMLHttpRequest(lub Fetch API) i nas艂uchiwacze zdarze艅 DOM w przegl膮darce oraz operacje na systemie plik贸w lub 偶膮dania sieciowe w Node.js. - P臋tla zdarze艅 (Event Loop): G艂贸wny komponent, kt贸ry nieustannie sprawdza, czy stos wywo艂a艅 jest pusty. Je艣li jest, a w kolejce zada艅 znajduj膮 si臋 zadania, p臋tla zdarze艅 przenosi pierwsze zadanie z kolejki zada艅 na stos wywo艂a艅 w celu wykonania.
- Kolejka mikrozada艅 (Microtask Queue): Kolejka przeznaczona specjalnie dla mikrozada艅, kt贸re maj膮 wy偶szy priorytet ni偶 zwyk艂e zadania. Mikrozadania s膮 zazwyczaj zwi膮zane z obiektami Promise i MutationObserver.
Jak dzia艂a p臋tla zdarze艅: Wyja艣nienie krok po kroku
- Wykonywanie kodu: JavaScript rozpoczyna wykonywanie kodu, umieszczaj膮c funkcje na stosie wywo艂a艅 w miar臋 ich wywo艂ywania.
- Operacja asynchroniczna: Gdy napotkana zostanie operacja asynchroniczna (np.
setTimeout,fetch), jest ona delegowana do Web API (lub API Node.js). - Obs艂uga przez Web API: Web API (lub API Node.js) obs艂uguje operacj臋 asynchroniczn膮 w tle. Nie blokuje to w膮tku JavaScript.
- Umieszczenie funkcji zwrotnej: Po zako艅czeniu operacji asynchronicznej, Web API (lub API Node.js) umieszcza odpowiedni膮 funkcj臋 zwrotn膮 (callback) w kolejce zada艅.
- Monitorowanie przez p臋tl臋 zdarze艅: P臋tla zdarze艅 nieustannie monitoruje stos wywo艂a艅 i kolejk臋 zada艅.
- Sprawdzenie, czy stos wywo艂a艅 jest pusty: P臋tla zdarze艅 sprawdza, czy stos wywo艂a艅 jest pusty.
- Przeniesienie zadania: Je艣li stos wywo艂a艅 jest pusty i w kolejce zada艅 znajduj膮 si臋 zadania, p臋tla zdarze艅 przenosi pierwsze zadanie z kolejki zada艅 na stos wywo艂a艅.
- Wykonanie funkcji zwrotnej: Funkcja zwrotna jest teraz wykonywana i mo偶e z kolei umie艣ci膰 wi臋cej funkcji na stosie wywo艂a艅.
- Wykonanie mikrozada艅: Po zako艅czeniu zadania (lub sekwencji zada艅 synchronicznych) i opr贸偶nieniu stosu wywo艂a艅, p臋tla zdarze艅 sprawdza kolejk臋 mikrozada艅. Je艣li s膮 w niej mikrozadania, s膮 one wykonywane jedno po drugim, a偶 kolejka mikrozada艅 b臋dzie pusta. Dopiero wtedy p臋tla zdarze艅 przejdzie do pobrania kolejnego zadania z kolejki zada艅.
- Powtarzanie: Proces powtarza si臋 w spos贸b ci膮g艂y, zapewniaj膮c, 偶e operacje asynchroniczne s膮 obs艂ugiwane wydajnie bez blokowania g艂贸wnego w膮tku.
Praktyczne przyk艂ady: P臋tla zdarze艅 w akcji
Przyk艂ad 1: setTimeout
Ten przyk艂ad pokazuje, jak setTimeout wykorzystuje p臋tl臋 zdarze艅 do wykonania funkcji zwrotnej po okre艣lonym op贸藕nieniu.
console.log('Start');
setTimeout(() => {
console.log('Timeout Callback');
}, 0);
console.log('End');
Wynik:
Start End Timeout Callback
Wyja艣nienie:
console.log('Start')jest wykonywane i natychmiast wypisywane na konsol臋.setTimeoutjest wywo艂ywane. Funkcja zwrotna i op贸藕nienie (0ms) s膮 przekazywane do Web API.- Web API uruchamia timer w tle.
console.log('End')jest wykonywane i natychmiast wypisywane na konsol臋.- Po zako艅czeniu timera (nawet je艣li op贸藕nienie wynosi 0ms), funkcja zwrotna jest umieszczana w kolejce zada艅.
- P臋tla zdarze艅 sprawdza, czy stos wywo艂a艅 jest pusty. Jest pusty, wi臋c funkcja zwrotna jest przenoszona z kolejki zada艅 na stos wywo艂a艅.
- Funkcja zwrotna
console.log('Timeout Callback')jest wykonywana i wypisywana na konsol臋.
Przyk艂ad 2: Fetch API (Promises)
Ten przyk艂ad pokazuje, jak Fetch API wykorzystuje obiekty Promise i kolejk臋 mikrozada艅 do obs艂ugi asynchronicznych 偶膮da艅 sieciowych.
console.log('Requesting data...');
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(data => console.log('Data received:', data))
.catch(error => console.error('Error:', error));
console.log('Request sent!');
(Zak艂adaj膮c, 偶e 偶膮danie zako艅czy si臋 sukcesem) Mo偶liwy wynik:
Requesting data...
Request sent!
Data received: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
Wyja艣nienie:
console.log('Requesting data...')jest wykonywane.fetchjest wywo艂ywane. 呕膮danie jest wysy艂ane do serwera (obs艂ugiwane przez Web API).console.log('Request sent!')jest wykonywane.- Gdy serwer odpowie, funkcje zwrotne
thens膮 umieszczane w kolejce mikrozada艅 (poniewa偶 u偶ywane s膮 obiekty Promise). - Po zako艅czeniu bie偶膮cego zadania (synchronicznej cz臋艣ci skryptu), p臋tla zdarze艅 sprawdza kolejk臋 mikrozada艅.
- Pierwsza funkcja zwrotna
then(response => response.json()) jest wykonywana, parsuj膮c odpowied藕 JSON. - Druga funkcja zwrotna
then(data => console.log('Data received:', data)) jest wykonywana, loguj膮c otrzymane dane. - Je艣li podczas 偶膮dania wyst膮pi b艂膮d, zamiast tego wykonywana jest funkcja zwrotna
catch.
Przyk艂ad 3: System plik贸w w Node.js
Ten przyk艂ad demonstruje asynchroniczne odczytywanie plik贸w w Node.js.
const fs = require('fs');
console.log('Reading file...');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) {
console.error('Error reading file:', err);
return;
}
console.log('File content:', data);
});
console.log('File read operation initiated.');
(Zak艂adaj膮c, 偶e plik 'example.txt' istnieje i zawiera 'Hello, world!') Mo偶liwy wynik:
Reading file... File read operation initiated. File content: Hello, world!
Wyja艣nienie:
console.log('Reading file...')jest wykonywane.fs.readFilejest wywo艂ywane. Operacja odczytu pliku jest delegowana do API Node.js.console.log('File read operation initiated.')jest wykonywane.- Po zako艅czeniu odczytu pliku, funkcja zwrotna jest umieszczana w kolejce zada艅.
- P臋tla zdarze艅 przenosi funkcj臋 zwrotn膮 z kolejki zada艅 na stos wywo艂a艅.
- Funkcja zwrotna (
(err, data) => { ... }) jest wykonywana, a zawarto艣膰 pliku jest logowana do konsoli.
Zrozumienie kolejki mikrozada艅
Kolejka mikrozada艅 jest kluczow膮 cz臋艣ci膮 p臋tli zdarze艅. S艂u偶y do obs艂ugi kr贸tkotrwa艂ych zada艅, kt贸re powinny zosta膰 wykonane natychmiast po zako艅czeniu bie偶膮cego zadania, ale przed pobraniem przez p臋tl臋 zdarze艅 nast臋pnego zadania z kolejki zada艅. Funkcje zwrotne z obiekt贸w Promise i MutationObserver s膮 zazwyczaj umieszczane w kolejce mikrozada艅.
Kluczowe cechy:
- Wy偶szy priorytet: Mikrozadania maj膮 wy偶szy priorytet ni偶 zwyk艂e zadania w kolejce zada艅.
- Natychmiastowe wykonanie: Mikrozadania s膮 wykonywane natychmiast po zako艅czeniu bie偶膮cego zadania i przed przetworzeniem przez p臋tl臋 zdarze艅 nast臋pnego zadania z kolejki zada艅.
- Wyczerpanie kolejki: P臋tla zdarze艅 b臋dzie kontynuowa膰 wykonywanie mikrozada艅 z kolejki mikrozada艅, a偶 kolejka b臋dzie pusta, zanim przejdzie do kolejki zada艅. Zapobiega to "g艂odzeniu" mikrozada艅 i zapewnia, 偶e s膮 one obs艂ugiwane niezw艂ocznie.
Przyk艂ad: Rozwi膮zanie obietnicy (Promise)
console.log('Start');
Promise.resolve().then(() => {
console.log('Promise resolved');
});
console.log('End');
Wynik:
Start End Promise resolved
Wyja艣nienie:
console.log('Start')jest wykonywane.Promise.resolve().then(...)tworzy rozwi膮zan膮 obietnic臋 (Promise). Funkcja zwrotnathenjest umieszczana w kolejce mikrozada艅.console.log('End')jest wykonywane.- Po zako艅czeniu bie偶膮cego zadania (synchronicznej cz臋艣ci skryptu), p臋tla zdarze艅 sprawdza kolejk臋 mikrozada艅.
- Funkcja zwrotna
then(console.log('Promise resolved')) jest wykonywana, loguj膮c komunikat do konsoli.
Async/Await: Sk艂adniowy lukier dla obiekt贸w Promise
S艂owa kluczowe async i await zapewniaj膮 bardziej czytelny i wygl膮daj膮cy synchronicznie spos贸b pracy z obiektami Promise. S膮 one w istocie lukrem sk艂adniowym nad obiektami Promise i nie zmieniaj膮 podstawowego zachowania p臋tli zdarze艅.
Przyk艂ad: U偶ycie Async/Await
async function fetchData() {
console.log('Requesting data...');
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await response.json();
console.log('Data received:', data);
} catch (error) {
console.error('Error:', error);
}
console.log('Function completed');
}
fetchData();
console.log('Fetch Data function called');
(Zak艂adaj膮c, 偶e 偶膮danie zako艅czy si臋 sukcesem) Mo偶liwy wynik:
Requesting data...
Fetch Data function called
Data received: { userId: 1, id: 1, title: 'delectus aut autem', completed: false }
Function completed
Wyja艣nienie:
fetchData()jest wywo艂ywane.console.log('Requesting data...')jest wykonywane.await fetch(...)wstrzymuje wykonanie funkcjifetchDatado momentu rozwi膮zania obietnicy (Promise) zwr贸conej przezfetch. Kontrola jest oddawana z powrotem do p臋tli zdarze艅.console.log('Fetch Data function called')jest wykonywane.- Gdy obietnica
fetchzostanie rozwi膮zana, wykonanie funkcjifetchDatajest wznawiane. response.json()jest wywo艂ywane, a s艂owo kluczoweawaitponownie wstrzymuje wykonanie do czasu uko艅czenia parsowania JSON.console.log('Data received:', data)jest wykonywane.console.log('Function completed')jest wykonywane.- Je艣li podczas 偶膮dania wyst膮pi b艂膮d, wykonywany jest blok
catch.
P臋tla zdarze艅 w r贸偶nych 艣rodowiskach: Przegl膮darka vs. Node.js
P臋tla zdarze艅 jest fundamentalnym poj臋ciem zar贸wno w 艣rodowiskach przegl膮darkowych, jak i Node.js, ale istniej膮 pewne kluczowe r贸偶nice w ich implementacjach i dost臋pnych API.
艢rodowisko przegl膮darki
- Web API: Przegl膮darka dostarcza Web API, takie jak
setTimeout,XMLHttpRequest(lub Fetch API), nas艂uchiwacze zdarze艅 DOM (np.addEventListener) oraz Web Workers. - Interakcje u偶ytkownika: P臋tla zdarze艅 jest kluczowa do obs艂ugi interakcji u偶ytkownika, takich jak klikni臋cia, naci艣ni臋cia klawiszy i ruchy myszy, bez blokowania g艂贸wnego w膮tku.
- Renderowanie: P臋tla zdarze艅 obs艂uguje r贸wnie偶 renderowanie interfejsu u偶ytkownika, zapewniaj膮c, 偶e przegl膮darka pozostaje responsywna.
艢rodowisko Node.js
- API Node.js: Node.js dostarcza w艂asny zestaw API do operacji asynchronicznych, takich jak operacje na systemie plik贸w (
fs.readFile), 偶膮dania sieciowe (u偶ywaj膮c modu艂贸w takich jakhttplubhttps) oraz interakcje z bazami danych. - Operacje I/O: P臋tla zdarze艅 jest szczeg贸lnie wa偶na do obs艂ugi operacji I/O w Node.js, poniewa偶 operacje te mog膮 by膰 czasoch艂onne i blokuj膮ce, je艣li nie s膮 obs艂ugiwane asynchronicznie.
- Libuv: Node.js u偶ywa biblioteki o nazwie
libuvdo zarz膮dzania p臋tl膮 zdarze艅 i asynchronicznymi operacjami I/O.
Dobre praktyki pracy z p臋tl膮 zdarze艅
- Unikaj blokowania g艂贸wnego w膮tku: D艂ugotrwa艂e operacje synchroniczne mog膮 zablokowa膰 g艂贸wny w膮tek i sprawi膰, 偶e aplikacja przestanie odpowiada膰. U偶ywaj operacji asynchronicznych, gdy tylko jest to mo偶liwe. Rozwa偶 u偶ycie Web Workers w przegl膮darkach lub worker threads w Node.js do zada艅 intensywnie wykorzystuj膮cych procesor.
- Optymalizuj funkcje zwrotne: Utrzymuj funkcje zwrotne kr贸tkie i wydajne, aby zminimalizowa膰 czas ich wykonywania. Je艣li funkcja zwrotna wykonuje z艂o偶one operacje, rozwa偶 podzielenie jej na mniejsze, 艂atwiejsze do zarz膮dzania cz臋艣ci.
- Obs艂uguj b艂臋dy prawid艂owo: Zawsze obs艂uguj b艂臋dy w operacjach asynchronicznych, aby zapobiec awarii aplikacji z powodu nieobs艂u偶onych wyj膮tk贸w. U偶ywaj blok贸w
try...catchlub handler贸wcatchw obiektach Promise, aby 艂apa膰 i obs艂ugiwa膰 b艂臋dy w elegancki spos贸b. - U偶ywaj obiekt贸w Promise i Async/Await: Obiekty Promise oraz async/await zapewniaj膮 bardziej ustrukturyzowany i czytelny spos贸b pracy z kodem asynchronicznym w por贸wnaniu z tradycyjnymi funkcjami zwrotnymi. U艂atwiaj膮 r贸wnie偶 obs艂ug臋 b艂臋d贸w i zarz膮dzanie asynchronicznym przep艂ywem sterowania.
- B膮d藕 艣wiadomy kolejki mikrozada艅: Zrozum zachowanie kolejki mikrozada艅 i jak wp艂ywa ona na kolejno艣膰 wykonywania operacji asynchronicznych. Unikaj dodawania zbyt d艂ugich lub z艂o偶onych mikrozada艅, poniewa偶 mog膮 one op贸藕ni膰 wykonywanie zwyk艂ych zada艅 z kolejki zada艅.
- Rozwa偶 u偶ycie strumieni (Streams): W przypadku du偶ych plik贸w lub strumieni danych, u偶ywaj strumieni do przetwarzania, aby unikn膮膰 艂adowania ca艂ego pliku do pami臋ci naraz.
Cz臋ste pu艂apki i jak ich unika膰
- Piek艂o callback贸w (Callback Hell): G艂臋boko zagnie偶d偶one funkcje zwrotne mog膮 sta膰 si臋 trudne do czytania i utrzymania. U偶ywaj obiekt贸w Promise lub async/await, aby unikn膮膰 piek艂a callback贸w i poprawi膰 czytelno艣膰 kodu.
- Zalgo: Zalgo odnosi si臋 do kodu, kt贸ry mo偶e wykonywa膰 si臋 synchronicznie lub asynchronicznie w zale偶no艣ci od danych wej艣ciowych. Ta nieprzewidywalno艣膰 mo偶e prowadzi膰 do nieoczekiwanego zachowania i trudnych do debugowania problem贸w. Upewnij si臋, 偶e operacje asynchroniczne zawsze wykonuj膮 si臋 asynchronicznie.
- Wycieki pami臋ci: Niezamierzone odwo艂ania do zmiennych lub obiekt贸w w funkcjach zwrotnych mog膮 uniemo偶liwi膰 ich usuni臋cie przez garbage collector, co prowadzi do wyciek贸w pami臋ci. Uwa偶aj na domkni臋cia (closures) i unikaj tworzenia niepotrzebnych odwo艂a艅.
- G艂odzenie (Starvation): Je艣li mikrozadania s膮 ci膮gle dodawane do kolejki mikrozada艅, mo偶e to uniemo偶liwi膰 wykonanie zada艅 z kolejki zada艅, prowadz膮c do g艂odzenia. Unikaj zbyt d艂ugich lub z艂o偶onych mikrozada艅.
- Nieobs艂u偶one odrzucenia obietnic (Unhandled Promise Rejections): Je艣li obietnica (Promise) zostanie odrzucona, a nie ma handlera
catch, odrzucenie pozostanie nieobs艂u偶one. Mo偶e to prowadzi膰 do nieoczekiwanego zachowania i potencjalnych awarii. Zawsze obs艂uguj odrzucenia obietnic, nawet je艣li ma to by膰 tylko zalogowanie b艂臋du.
Kwestie internacjonalizacji (i18n)
Podczas tworzenia aplikacji, kt贸re obs艂uguj膮 operacje asynchroniczne i p臋tl臋 zdarze艅, wa偶ne jest, aby wzi膮膰 pod uwag臋 internacjonalizacj臋 (i18n), aby zapewni膰, 偶e aplikacja dzia艂a poprawnie dla u偶ytkownik贸w w r贸偶nych regionach i z r贸偶nymi j臋zykami. Oto kilka kwestii do rozwa偶enia:
- Formatowanie daty i czasu: U偶ywaj odpowiedniego formatowania daty i czasu dla r贸偶nych lokalizacji podczas obs艂ugi operacji asynchronicznych obejmuj膮cych timery lub harmonogramowanie. Mog膮 w tym pom贸c biblioteki takie jak
Intl.DateTimeFormat. Na przyk艂ad daty w Japonii s膮 cz臋sto formatowane jako RRRR/MM/DD, podczas gdy w USA zazwyczaj jako MM/DD/RRRR. - Formatowanie liczb: U偶ywaj odpowiedniego formatowania liczb dla r贸偶nych lokalizacji podczas obs艂ugi operacji asynchronicznych obejmuj膮cych dane liczbowe. Mog膮 w tym pom贸c biblioteki takie jak
Intl.NumberFormat. Na przyk艂ad separatorem tysi臋cy w niekt贸rych krajach europejskich jest kropka (.), a nie przecinek (,). - Kodowanie tekstu: Upewnij si臋, 偶e aplikacja u偶ywa prawid艂owego kodowania tekstu (np. UTF-8) podczas obs艂ugi operacji asynchronicznych obejmuj膮cych dane tekstowe, takich jak odczyt lub zapis plik贸w. R贸偶ne j臋zyki mog膮 wymaga膰 r贸偶nych zestaw贸w znak贸w.
- Lokalizacja komunikat贸w o b艂臋dach: Lokalizuj komunikaty o b艂臋dach wy艣wietlane u偶ytkownikowi w wyniku operacji asynchronicznych. Zapewnij t艂umaczenia dla r贸偶nych j臋zyk贸w, aby u偶ytkownicy rozumieli komunikaty w swoim ojczystym j臋zyku.
- Uk艂ad od prawej do lewej (RTL): Rozwa偶 wp艂yw uk艂ad贸w RTL na interfejs u偶ytkownika aplikacji, zw艂aszcza podczas obs艂ugi asynchronicznych aktualizacji interfejsu. Upewnij si臋, 偶e uk艂ad poprawnie dostosowuje si臋 do j臋zyk贸w RTL.
- Strefy czasowe: Je艣li Twoja aplikacja zajmuje si臋 harmonogramowaniem lub wy艣wietlaniem czas贸w w r贸偶nych regionach, kluczowe jest prawid艂owe obs艂ugiwanie stref czasowych, aby unikn膮膰 rozbie偶no艣ci i dezorientacji u偶ytkownik贸w. Biblioteki takie jak Moment Timezone (cho膰 obecnie jest w trybie konserwacji, nale偶y poszuka膰 alternatyw) mog膮 pom贸c w zarz膮dzaniu strefami czasowymi.
Wnioski
P臋tla zdarze艅 JavaScript jest kamieniem w臋gielnym programowania asynchronicznego w JavaScript. Zrozumienie jej dzia艂ania jest niezb臋dne do pisania wydajnych, responsywnych i nieblokuj膮cych aplikacji. Poprzez opanowanie poj臋膰 takich jak stos wywo艂a艅, kolejka zada艅, kolejka mikrozada艅 i Web API, programi艣ci mog膮 wykorzysta膰 moc programowania asynchronicznego do tworzenia lepszych do艣wiadcze艅 u偶ytkownika zar贸wno w 艣rodowiskach przegl膮darkowych, jak i Node.js. Przyj臋cie dobrych praktyk i unikanie cz臋stych pu艂apek doprowadzi do tworzenia bardziej solidnego i 艂atwiejszego w utrzymaniu kodu. Ci膮g艂e odkrywanie i eksperymentowanie z p臋tl膮 zdarze艅 pog艂臋bi Twoje zrozumienie i pozwoli Ci z pewno艣ci膮 siebie stawia膰 czo艂a z艂o偶onym wyzwaniom asynchronicznym.