Uvod
Pri sinhronom izvršavanju operacija, trenutna operacija mora da se završi da bi naredna počela da se izvršava. Dok kod asihronog izvršavanja, operacije ne čekaju jedna drugu da se izvrše, nego se izvršavaju istovremeno. Generalno u većini slučajeva sinhronomi procesi nisu problem jer su kompjuteri veoma brzi pa se sve operacije izvršavaju skoro instant. Medjutim kod web-a su problem upiti ka serveru. Ukoliko bi ovi procesi bili sinhroni, ceo interfejs web aplikacije bi bio “smrznut” dok ne stigne odgovor servera. Isto važi za I/O operacije sa podacima. Rezultat toga je da se ukupno asinhrone operacije brže izvršavaju iako svaka pojedinačno ništa nije brža nego kod sinhronih operacija. (Pogledaj sliku)
JavaScript i asinhrono programiranje
JavaScript je “single thread” programski jezik i on sam ne može da izvršava više istovremenih paralelnih radnji. JavaScript je takodje i skriptni jezik stoga je neophodno da se “smesti” u neki kontejner koji bi mu omogućio rad, taj kontejner se zove JS Runtime Environment (srp. okruženje). Upravo je to okruženje zaslužno za izvršavanje više paralelnih radnji istovremeno (tzv. asihrono izvršavanje). Ključni delovi zaduženi za asihroni rad JS-a su “External API”, i “Event loop”. Stoga da bi razumeli kako JavaScript obradjuje asinhrone operacije, neophodno je prvo upoznati osnovne elemente Runtime Enviroment-a.
Postoje dva tipa okruženja: browser (za rad na klijentu) i node.js (za rad na serveru). Runtime Environment čine :
- JS Engine/Runtime
- Heap
- Stack
- External API
- Queue
- Event loop
Više o JS Runtime Enviroment-u pročitajte u članku “JavaScript i njegovo okruženje”
Koji su procesi asinhroni u JavaScriptu?
a) Izvršavanje setTimeout() metode
Asihrono programiranje sa odgovarajućim external API-jem je najlakše objasniti kroz primere. Naredni primer se izvršava u browseru, a kod sadrži dve metode za štampanje teksta u konzoli i jednu specijalnu metodu setTimeout() koja je deo objekta Window (WebAPI).
Primer
JS izvšava kod liniju po liniju, prvo stavi na stack prvu console.log() metodu i zatim je izvrši. Nakon čega JS engine dolazi do reda gde se poziva setTimeout(), pa ovu metodu stavlja na vrh stack-a i izvršava je.
Ovde počinje interesantni deo jer nakon izvršenja setTimeout() metode ona se sklanja sa stack-a, a njena callback funkcija se prebacuje u sekciju webAPI i uključuje tajmer. Nakon toga JS engine nastavlja sa obradom naredne linije koda gde nailazi na drugu console.log() metodu koju stavlja na stack i izvršava je.
Dok se izvršava ostali deo programa, a po isteku vremena zadatog u tajmeru callback funkcija se prebacuje na task queue i tamo čeka da se stack izprazni da bi mogla da se izrši.
Event loop će prebaciti task (callback funkciju) iz task queue samo ako je prazan stack.
Kada se callback funkcija nadje na stack-u izvršava se i nakon toga sklanja sa stack-a. Rezultat izvšavanja ovog koda je štampanje teksta u konzoli ovim redosledom:
- Prva console.log() metoda štampa: “Hi”
- Treća console.log() metoda štampa: “JSconfEU“
- Druga console.log() metoda u okviru setTimout() metode štampa: “there“
NAPOMENA:
Odlaganje callback funkcije za nula sekundi ne podrazumeva da će odmah da se izvrši. Callback funkcija mora da predje isti put kao i u prethodnom primeru (stack -> web API -> task queue -> stack), stim što će se u web APi sekciji zadržati nula sekundi. Medjutim u task queue će morati da sačeka dok se se stack ne isprazni. Ova “fora” se koristi kada želimo da se sinhroni proces postane asinhroni, tako što callback funkciju omotamo sa setTimout() metodom, a kao parametar stavimo 0ms.
Pogledajte animaciju ovog primera ovde (Savet: kliknite na ikonicu dva ukrštena čekića u gornjem levom uglu aplikacije da promenite brzinu izvršavanja koda).
b) Zahtev podataka sa servera
Pored tajmera koji omogućavaju asinhrono programiranje, asihron je i poziv serveru uz pomoć XMLHttpRequest objekta. Na sledećem primeru ćemo objasniti kako asinhrono funcioniše JavaScript pri pozivu servera:
Primer
1 2 3 4 5 |
console.log("Hi"); $.get("url", function cb (data) { console.log(data); }) console.log("SJS"); |
Prvo se izvšava prva linija koda zbog čega se prebacuje na stack
Kada se izvrši prva naredba ona se sklanja sa stack-a i izvršava se naredna a to je get() metoda koja bi trebalo da asinhrono “dovuče” podatke sa servera koristeći “HTTP GET request“.
Kad se izvrši metoda get(), ona se sklanja sa stack-a, dok “callback” funkcija stoji sa strane i tako ne koči dalje izvršavanje JavaScript koda dok čeka podatke.
Dok callback funkcija čeka podatke, paralelno se izvršava poslednja linija koda koja štampa u konzoli neki tekst.
Kada je ištampan teks i izvršena poslednja naredba, ona se sklanja sa stack-a koji ostaje prazan. Za to vreme je callback funkcija dobila podatke i smestila se u red za čekanje “queue”.
Čim se oslobodio stack, event loop prebacuje callback funkciju na stack i izvršava je.
Rezultat izvšavanja ovog koda je štampanje teksta u konzoli ovim redosledom:
- Prva console.log() metoda štampa: “Hi”
- Treća console.log() metoda štampa: “SJS“
- Druga console.log() metoda u okviru setTimout() metode štampa podatke sa servera
c) Dogadjaji se izvršavaju asinhrono
Svi dogadjaji u JavaScriptu se izvršavaju asinhrono, jer pri kompajliranju koda JS engine stavlja callback funkcije vezane za dogadjaje sa strane i tako ne blokira izvršavanje ostalog koda.
Primer
U ovom primeru pri svakom kliku mišem se “okinu” tri dogadjaja i aktiviraju se tri callback funkcije koje ne blokiraju izvršavanje Javascript koda već se prosledjuju u “queue” gde čekaju da se isprazni stack.
Pogledajte animaciju ovog primera ovde (Savet: kliknite na ikonicu dva ukrštena čekića u gornjem levom uglu aplikacije da promenite brzinu izvršavanja koda).
NAPOMENA:
Ne blokirajte event loop!!! Ceo princip asinhronog rada JavaScript-a i Runtime Environment-a se zasniva na tome da se delovi koda prosledjuju iz “queue” na stack sa Event Loop-om, medjutim ukoliko je stack stalno zauzet (npr. neki veliki while loop, ili neki zahtevan rendering…), tada nismo u mogućnosti da izvršimo asinhronu radnju koja čeka u “queue”.