Uvod


pyramid of doom

U JavaScript-u postoji izraz za deo koda sa ugnježdenim callback funkcijama pod nazivom “callback hell” ili “pyramid of doom”. Debagovanje takvog programskog koda pa čak i samo razumevanje je veoma otežano. Sa ES2015 standardom je došla nova syntax-a pod nazivom “promise” (srp. obećanje), koja sa svojim API-jem obezbedjuje bolji i pregledniji način za organizovanje callback funkcija. Ovo se naručito primećuje u radu sa asinhronim operacijama jer sa promisima sintaksa veoma liči na standardnu sinhronu sintaksu.

Primer – “callback hell”

Najpoznatija i najčešća primena callback funkcija je “hendlovanje” akcija nakon izvršenog dogadjaja. Upravo je “hendlovanje” medjusobno zavisnih dogadjaja najčešće uzrok “callback hell-a”. U ovom primeru je prikazan izgled takvog koda:

Šta je Promise?

Promise je javascript objekat koji predstavlja “placeholder” za rezultate asihrone funkcije sve dok traje izvršavanje asinhrone operacije.

Promise je sinhrono vraćen objekat pri asinhronoj operaciji, koji predstavlja privremenu zamenu za moguće rezultate te asinhrone operacije.

Promise umesto krajnje vrednosti, daje obećanje da će dostaviti tu vrednost u nekom trenutku u budućnosti. Pošto su promise objekti privremena zamena za buduću eventualnu vrednost, to nam omogućava da preko njega zakačimo handler-e za budući rezultat asinhrone operacije. Sa ovom novom mogućnosti smo skoro izjednačili asinhrone operacije sa sinhronim. Sada i sinhrone i asinhrone operacije mogu da vraćaju neku vrednosti, stim što sinhrone odmah vraćaju krajnji podatak a asinhrone “placeholder” za budući podatak.

promise state

Asinhrona funkcija može imati dva moguća krajnja rezultata, a to su “uspešno izvršena operacija” ili “neuspešno izvršena opercija”, dok se promise može nalaziti u jednom od tri stanja:

  • Pending – kada se asinhrona radnja još uvek izvršava
  • Fulfill – kada je asinhrona radnja završena uspešno
  • Reject – kada je asinhrona radnja neuspešno završena greškom

Ceo promise mehanizam se može podeliti na dva dela:

  1. Kreiranje promisa unutar asinhrone funkcije
  2. Korišćenje kreiranog promisa (kod se nalazi izvan asinhrone funkcije)

Kreiranje promisa

Da bi se budući krajnji rezultat asinhrone funkcije zamenio sa promise objektom, potrebno je da funkcija vrati novi promise objekat kroz svoj kod:

Svakom novom obećanju se kroz parametar prosledjuje funkcija (tzv. executor funkcija) koja obradjuje samu asinhronu operaciju i buduće rezultate te asinhrone operacije. Nama je interesantan deo gde obradjuje eventualne rezultate asinhrone operacije, kada u zavisnosti od uspešnosti operacije poziva jednu od dve funkcije koje su joj prosledjene kao parametri:

  • Funkcija resolve() se poziva u delu koda koji obradjuje uspešno završenu asinhronu operaciju. Parametar ove funkcije predstavlja dobijeni podatak iz uspešno završene operacije, stoga se funkcija resolve() koristi da kroz svoj parametar prosledi rezultujući podatak odgovarajućoj “handler” metodi npr. then() ili Promise.all()…
  • Funkcija “reject()” se poziva u delu koda koji obradjuje slučaj kada se pojavi problem sa izvršavanjem asinhrone operacije. Ona kroz svoj parametar prosledjuje razlog neuspešnosti asinhrone operacije odgovarajućem “hendleru”, najčešće catch() metodi.

Korišćenje promisa

Korišćenje promisa podrazumeva obradu eventualnih rezultata dobijenih po završetku asinhrone operacije.

Promise.prototype.then()

Promise reaguje na promenu svoga stanja pozivajući callback funkciju. Ukoliko je nakon promene stanja promise u stanju fullfiled poziva se metoda then(). Ova metoda prihvata dva parametra “onFulfilled” i “onRejected” koji su tipa funkcije.

Prva funkcija onFulfilled “hendluje” uspešno završenu asinhronu operaciju. Ona prihvata jedan parametar a kroz njega joj se prosledjuje podatak dobijen asinhronom operacijom. Dok druga funkcija “onRejected” “hendluje” neuspešno završenu asinhronu operaciju i takodje prihvata jedan parametar kroz koji joj se prosledjuje razlog neuspeha.

Primer

U ovom primeru kroz parametar funkcije resolve() prosledjujemo podatak (xhr.response) funkciji onFulfilled(), dok kroz parametar funkcije reject() prosledjujemo tip greške “new Error(xhr.statusText)” funkciji onRejected().

Promise.prototype.catch()

Ukoliko je nakon promene stanja, promise u stanju rejected, poziva se metoda catch().

Metoda catch() kao parametar prihvata callback funkciju (za koju se koristi naziv “onRejected()”), koja je zadužena za prihvatanje i obradu greške.

OBJAŠNJENJE:
Iako metoda then() može da “hendluje” pored uspešnih rezultata i neuspešne, to se ne koristi tako često. Najčešće se metoda then() koristi da “hendluje” samo uspešan rezultat (koristi se samo prvi parametar), dok se za slučaj neuspešne operacije koristi catch() metoda.

Ili malo preglednije napisano:

Primer

U primeru je prikazano hendlovanje ajax asinhrone operacije sa promisom koji koristi then() i catch() metodu ali i sa “zastarelim” callback funkcijama:

U ovom primeru je preko parametra funkcije resolve() prosledjen “xhr.responseText” u “data” parametar then() metode, a preko parametra funkcije reject() je prosledjen “new Error(“Network error”)” u “err”. parametar catch() metode


Ulančavanje promisa

Pošto metode then() i catch(), uvek vraćaju novi promise, one se mogu ulančavati. Koristeći ovu mogućnost se otvaraju vrata da se na elegantan nači reši problem sa mnogo ulančanih dogadjaja koje pozivaju callback funkcije, poznatiji kao “callback hell”.

Primer

Promise.resolve()

Promise.resolve(value) je metoda koja pravi promise od drugačijih tipova. Može da primi tri različita tipa kao parametar, nakon čega vraća promise:

  1. Promise.resolve(vrednost)
    Kada se ovoj metodi prosledi neka vrednost, ta vrednost će biti prosledjena then() funkciji kroz njen parametar.
  2. Promise.resolve(drugiPromise)
    Kada se prosledi neki drugi promise tada se kroz parametar metode then() prosledjuje eventualno stanje prihvaćenog promise objekta. Ovo je najčešći slučaj, jer se koristi za konvertovanje promisa kreiranih od strane drugih biblioteka.
    Primer

    Obratiti pažnju na redosled štampanja u konzoli, redosled je drugačiji nego što je u kodu, zato što je “then handlers” pozvan asinhrono. Više o ovome pogledajte u članku Osnove asinhronog programiranja u JavaScript-u

  3. Promise.resolve(thenableObject)
    Ova metoda može da prihvati kao parametar i tzv. “thenable object”:
    Primer

Promise.reject()

Promise.reject(reason) uzima za “reason object” String ili Error, a vraća promise koji se nalazi u stanju rejected uz odgovarajući razlog odbijanja.

Promise.all()

Promise.all(iterable) kao parametar uzima iterabilnu listu promise objekata. Promise.all() metod vraća jedan promise u trenutku kada su svi promisi sa liste uspešno rešeni. Promise.all() metoda će vratiti “promis”, čak i kada joj se prosledi prazna lista ili element koji nije promise (pogledaj primer). Kod ove metode redosled izvršavanja liste promisa nije zagarantovan, jedino je zagarantovano da će vratiti krajnji “promis”, kada svi promisi sa liste budu fullfiled.

Primer

U slučaju da je jedan promise iz liste u stanju rejected, metoda Promise.all() vraća odbijen promise bez obzira da li u listi postoji neki promis koji je uspešan. Uz odbijen promis se vraća i razlog odbijanja. U slučaju da ima više odbijenih promisa, prosledjeni razlog je od prvog odbijenog promisa!

Asinhronost i sinhronost Promise.all()

Promise.all() se u svim slučajevima ponaša asinhrono osim u slučaju kada je umesto liste prosledjen prazan objekt, tada se ponaša sinhrono.

Primer

Obratite pažnju da je pri direktnom sinhronom stampanju “prazanPromise” u stanju fullfiled, dok je standardni promise pod nazivom “nekiPromise” u stanju pending.

Kada koristiti “Promise.all()” a kada “Promise.prototype.then()”?

Izbor se svodi na to da li je bitan redosled izvršavanja promisa ili ne. U sledećem primeru je bitan redosled izvršavanja promisa pa se koristi Promise.prototype.then():

Dok u narednom primeru za pravljenje betona nije bitan redosled kada će koji promise biti završen, već je samo bitan trenutak kada su svi spremni, pa je preporuka da se koristi Promise.all():

Promise.race()

Promise.race(iterable) je metoda koja takodje kao parametar uzima iterabilnu listu promise objekata a vraća obećanje. Ova metoda vraća rezultat koji “stigne prvi”, nebitno da li je uspešan ili ne. Pa tako može biti vraćen promise ili sa krajnjim podatkom kod uspešno izvršene asinhrone operacije ili sa razlogom za neuspešnu operaciju.

Podelite:

5 Responses to “Promise (osnove)”

  1. Misko

    Svaka čast na objašnjavanju i trudu. Tek kad neko pokuša da obajsni postaje uočljivo se koliki idiot je smišljao ovaj programski jezik i koliko je ovo zavitlavanje ljudi.

Ostavite komentar