Uvod
Korišćenje samo jednog promisa je jasno i jednostavno, medjutim kada se program zakomplikuje asinhronom logikom, rad sa promisima se rapidno otežava. Sa ES2017 standardnom je stigla i nova sintaksa “async/await”, koja olakšava rad sa promisima, i omogućava jednostavnije predstavljanje serije asinhronih promisa. Korišćenje “async/await” sintakse nam omogućava da višestruke medjusobno zavisne asinhrone radnje, prikažemo čitljivije i tako izbegnemo tzv. “promise hell”. Treba napomenuti da se uz async funcije ne mogu koristiti “obične” callback funkcije.
“Async/await” sintaksa ne isključuje promise, nego menja način “konzumacije” promisa. Async/Await sintaksa nam omogućava da pišemo asinhroni kod prema sekvencijalnom redosledu izvršavanja tako da liči na sinhroni kod.
Sintaksa asinhrone funkcije
Obeležavanje funkcije sa “async”
Asinhrona funkcija je funkcija unutar koje se izvršava asinhroni kod. Asinhrona funkcija je obeležena se sa ključnom reči “async”. Tako obeležena funkcija daje do znanja kompajleru da će se unutar nje izvršiti asinhrona operacija.
1 |
async function nekaAsinhornaFunkcija(){...} |
Taskovi koje async funkcija vrši u pozadini su:
- Automatski konvertuje regularnu funkciju u promise
- Sve što je vraćeno sa “return” u telu funkcije, nakon uspešne operacije postaje “Promise.resolve”
1234function getNumber() { = async function getNumber() {return Promise.resolve(4) = return 4 // vraća Promise.resolve(4)} = }getNumber().then(res => console.log(res)); = getNumber().then(res => console.log(res)); //Vraća: 4
Async funkcija uvek vraća “promise”, čak i ako vraćena vrednost nije “promise”. Async funkcija “obavija” svaku vraćenu vrednost i prosledjuje je kao Promise. - Async funkcija omogućava korišćenje await operatora.
“Await” operator
Ovaj operator može da se koristi jedino u okviru “async” funkcije, gde pauzira izvršenje async funkcije. Rezervisana reč “await” pauzira izvršenje async funkcije sve dok ne doobije rezultate operacije tj. dok se ne vrati promise. Ako je Promise u stanju fulfilled, onda je rezulta koji je “dočekao” await ima “fulfillment” vrednost, a ako je dočekani Promise u stanju “rejected” onda await prosledjuje “rejection” vrednost.
Error handling
Uz operator await se koristi standardna try/catch sintaksa. Operator await “čeka” promise, i ukoliko je operacija uspešna, async funkcija prosledjuje “promise resolved”. U slučaju neuspešne operacije, prosledjuje “promise rejected”, dok se za “hvatanje” tog “exception-a” koristi “catch” blok.
1 2 3 4 5 6 7 8 9 |
function displayUserData() { return fetch('/users/pera') .then(function(pera) { console.log(pera); }) .catch(function (err) { console.error('Uhh imamo problem!', err); }) }) |
1 2 3 4 5 6 7 8 9 |
async function displayUserData() { try { let pera = await fetch('/users/pera'); console.log(pera); } catch(err) { console.error('Uhh imamo problem!', err); } } |
Objašnjenje:
Koristeći async/await sintaksu omogućavamo javascritpu da bolje upravlja greškama (naručito stack trace), i da na efikasniji način koristi memoriju (eng. memory-efficient).
Paralelizam
Async operacije se izvršavaju u serijama jedna za drugom, pa ih ne treba mešati sa “Promise.all” gde se promisi izvršavaju paralelno. Za paralelno izvršavanje promisa je najbolje da koristitmo “Promise.all” sintaksu. Treba naglasiti da “async/await” sintaksa ne isključuje “promise.all” sintaksu, čak šta više zajedno daju svoj maksimum a kod je jasan i pregledan. Kroz naredne primere će biti prikazana razlika izmedju seriskog i paralelnog izvršavanja.
Serijsko izvršavanje
Ovo je primer seriskog izvršavanja jer drugi “await” čeka da se izvrši prva operacija.
1 2 3 4 |
async function series() { const result1 = await wait(500); const result2 = await wait(500); } // Ukupno traje 1000ms! |
Paralelno izvršavanje
Ukoliko želimo da se operacije izvršavaju paralelno potrebno je da pustimo da se obe funkcije paralelno izvode, a tek onda da ih “sačekamo” await operatorom:
1 2 3 4 5 6 7 |
async function parallel() { const result1 = wait(500); const result2 = wait(500); await result1; await result2; return "done!"; } // Ukupno traje samo 500ms! |
Simbioza Promise.all i async/await sintaksi
1 2 3 4 5 6 |
async function foo() { const results = await Promise.all([ wait(500), wait(500) ]); } |
Sa destruktuiranjem možemo da pojedinačno dodelimo vraćene vrednosti:
1 2 3 4 5 6 |
async function foo() { const [result1, result2] = await Promise.all([ wait(500), wait(500) ]); } |
Iteracija asinhrone operacije
for…of
Preporuka je da se za iteraciju u asinhronoj funkciji koristi “for…of” petlja:
1 2 3 4 5 6 |
async function obradaNizaPromisa (){ for (const item of niz) { await asinhrona (item); } console.log("Ovo će se štampati posle asinhronih operacija :) ") } |
Problemi sa metodama za iteraciju nizova
Metoda za iteraciju kroz nizove, kao što je “forEach()” se ne može koristiti sa async/await sintaksom. Korišćenje “await” operatora unutar metode izbacuje error, a greška nastaje iz razloga što “await” operator može da postoji samo u okviru async funkcije, a u ovom slučaju bi se nalazio u okviru forEach() metode.
1 2 3 4 5 |
async function obradaNizaPromisa (){ niz.forEach(item => { await asinhrona (item); }) } // IZBACUJE ERROR |
Čak i ukoliko bi u okviru forEach() anonimnu callback funkciju definisali kao asinhronu, to bi samo pomoglo da ne izbacuje error, ali to ne bi dalo očekivani rezultat, jer metoda “forEach” ne “čeka” asinhronu operaciju, već nastavlja izvršavanje sinhronog koda.
1 2 3 4 5 6 |
async function obradaNizaPromisa (){ niz.forEach(async (item) => { await asinhrona (item); }) console.log("Ovo je sinhrona radnja koja će se nažalost izvršiti pre asinhronih") } |
Primeri primene Async/Await sintakse
U ovoj sekciji je opisan postupak prebacivanja sintakse sa “plain” promise u novu poboljšanu Async/Await sintaksu.
Pojedinačne asihrone operacije
a) Jednostavna asinhrona operacija
1 2 3 4 5 6 |
function asyncFunc() { return otherAsyncFunc() .catch(err => { console.error(err); }); } |
1 2 3 4 5 6 7 |
async function asyncFunc() { try { await otherAsyncFunc(); } catch (err) { console.error(err); } } |
b) XMLHttpRequest()
U ovome primeru su prikazani snippeti za dobijanje podataka u vidu JSON-a, na dva načina: prvi koji koristi HMLHttpRequest() i Promise i drugi gde je primenjena nova Async/Await sintaksa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function getJSON (url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); resolve(data); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = () => reject(new Error("Network error")); xhr.send(); }); } getJSON('https://api.myjson.com/bins/erxi9') .then( function(data){ console.log(data); return "Done" }) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function getJSON (url) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); resolve(data); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = () => reject(new Error("Network error")); xhr.send(); }); } async function serializeJSON(url) { const data1 = await getJSON(url); return data1; } serializeJSON('https://api.myjson.com/bins/erxi9') .then( function(data){ console.log(data); return "Done" }) |
c) fetch()
1 2 3 4 5 6 |
fetch('/users.json') .then(response => response.json()) .then(json => { console.log(json); }) .catch(e => { console.log('error!'); }) |
1 2 3 4 5 6 7 8 9 10 |
async function getJson() { try { let response = await fetch('/users.json'); let json = await response.json(); console.log(json); } catch(e) { console.log('Error!', e); } } |
Serija sekvencijalnih promisa
1 2 3 4 5 6 7 8 9 10 |
function asyncFunc() { return otherAsyncFunc1() .then(result1 => { console.log(result1); return otherAsyncFunc2(); }) .then(result2 => { console.log(result2); }); } |
1 2 3 4 5 6 |
async function asyncFunc() { const result1 = await otherAsyncFunc1(); console.log(result1); const result2 = await otherAsyncFunc2(); console.log(result2); } |
Serija sekvencijalnih medusobno zavisnih promisa
Na ovakavom primeru se najbolje vide sve prednosti i poboljšanja koja donosi nova sintaksa. Prvo je prikazana serija povezanih promisa samo sa promisima (bez Async/Await):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Osnovni promise function doubleAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x * 2); }, 2000); }); } // Serijalizovanje više promisa function serializePromise(x){ return new Promise(resolve => { doubleAfter2Seconds(x).then((a) => { doubleAfter2Seconds(a).then((b) => { doubleAfter2Seconds(b).then((c) => { resolve(x + a + b + c); }) }) }) }); } serializePromise(10).then((sum) => { console.log(sum); // 10+20+40+80=150 }); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function doubleAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x * 2); }, 2000); }); } async function serializeAsync(x) { const a = await doubleAfter2Seconds(x); const b = await doubleAfter2Seconds(a); const c = await doubleAfter2Seconds(b); return x + a + b + c; } serializeAsync(10).then((sum) => { console.log(sum); // 10+20+40+80=150 }); |
Serija višestrukih paralelnih asinhronih operacija
1 2 3 4 5 6 7 8 9 |
function asyncFunc() { return Promise.all([ otherAsyncFunc1(), otherAsyncFunc2(), ]) .then([result1, result2] => { console.log(result1, result2); }); } |
1 2 3 4 5 6 7 |
async function asyncFunc() { const [result1, result2] = await Promise.all([ otherAsyncFunc1(), otherAsyncFunc2(), ]); console.log(result1, result2); } |