Promise me future

U prethodnom tekstu dotakao sam se pojma asinhronih operacija. One se izvršavaju nezavisno od threada koji ih je startovao, a rezultat asinhrone operacije se može očekivati bilo kada.

Postoji više softverskih konstrukta kojima se implementiraju asinhrone aplikacije. Najprostiji način je izvršavanje asinhronog koda u posebnom threadu koji callbackom obaveštava kada je posao završen. To je previše jednostavno za iole ozbiljniji rad sa asinhronim operacijama - teško je uvezivati ih, hvatati greške i sl.

Bolje implementacije su Future i Promise.

Future

Future je softverski konstrukt koji predstavlja handle za rezultat koji će se izračunati u budućnosti. Future je read-only kontejner za vrednost koja još uvek ne postoji. Future se dobija asinhrono, ali ne znači da je non-blocking. To zavisi od momenta u kome zatreba vrednost koju future predstavlja. Primer:

Future<Double> primeFuture = runAsync(() -> {
    return calculateBigPrime();
});

// ...šta god...

primeFuture.get();	// blokirajuće

Futur se u Javi konstruiše pomoću ExecutorService; kako god, gornji primer se ne razlikuje mnogo u drugim jezicima. Iako prosleđujemo kod kojim se određuje vrednost za future, suština je da ga on ne čini, već da future određuje isključivo izračunata vrednost.

Iako zgodan, future nije dovoljan. Pošto se po prirodi ne može menjati, nema lakog kombinovanja s drugom operacijom, niti nastavljanja asinhronog toka dalje. U trenutku kada nam zatreba vrednost, moramo je zahtevati sa get(), što može da blokira trenutni tok izvršavanja ukoliko vrednost koju future predstavlja još uvek nije dostupna.

Promise

Promise je softverski konstrukt koji “obećava” da će proizvesti rezultat. Promise je kontejner i za varijablu i za dodeljivanje vrednosti varijabli - nije read-only; uključuje dakle i sam kod dodele. Promise vraća svoju varijablu kao future i može ga kompletirati (rezultatom ili exceptionom). Promise se može zamisliti kao future sa javnom set() metodom kroz koju se može upisati vrednost za future koji vraća.

Evo sjajnog primera koji ilustruje Promise:

Supplier<Integer> novcanik = ()-> {
	try {
		Thread.sleep(1000); 	// mama je zauzeta
    } catch (InterruptedException e) {}
	return 100;
};

CompletableFuture<Integer> promise =
	CompletableFuture.supplyAsync(momsPurse);

Svi smo nekada kamčili novac od roditelja. U ovom primeru ga tražimo od mame, koja obećava da će dati, ali ne odmah. Mama nam je, dakle, vratila promise . Kao dobro vaspitani klinci, zahvalićemo se onog trenutka kada novac dobijemo:

promise.thenAccept(v -> System.out.println("Hvala za " + v));

Ova linija upravo pokazuje svu lepotu promisa: nastavljanje toka, tj. povezivanje operacija. Da, future su monadi asihronog programiranja.

Na scenu stupa strogi tata, koji zaključuje da je mamin plan suviše darežljiv, te odlučuje da smanji novčani iznos dok je mama još uvek zauzeta:

promise.complete(10);

To momentalno ispunjava promise i mi kažemo:

> Hvala za 10

Mamin promise je ispunjen, ali ne na način kako je to prvobitno zamišljeno.

Sumarno i Sažeto

TL;DR: future i promise su dve strane asinhrone operacije: consumer i producer.