Dart – Futures

Webový vývojár dennodenne pracuje s ajaxovými requestami, callbackmi, obsluhovaním eventov. Neblokujúce API je v Javascripte štandardom, no nie je vždy úplne najpohodlnejšie používať ho. Dart rieši asynchrónne operácie pomocou Futures.

Seriál: Úvod do Dartu (9 dílů)

  1. Dart – Čo? Prečo? 2.8.2013
  2. Dart – Úvod do jazyka 23.8.2013
  3. Dart – Ponorme sa hlbšie 6.9.2013
  4. Dart – v DOMe 19.9.2013
  5. Dart – Futures 4.10.2013
  6. Dart ­– Streams 17.10.2013
  7. Dart – Používame JavaScript 1.11.2013
  8. Dart Typesystem 19.11.2013
  9. Dart – Neznesiteľná ľahkosť asynchrónneho bytia 2.12.2013

Predstavme si, že máme sadu asynchrónnych operácií, ktoré potrebujeme vykonať v presne danom poradí. Typické riešenie pomocou callbackov by vyzeralo nasledovne:

void main() {
  ajaxA(onSuccess: (resultA) {
    ajaxB(onSuccess: (resultB) {
      ajaxC(onSuccess: (resultC) {
        handleC(resultC);
      }, onError: () => handleError());
    }, onError: () => handleError());
  }, onError: () => handleError());
}

Takýto kód nie je úplne prehľadný.

Použime Futures!

V Darte asynchrónne operácie neočakávajú callback, miesto toho vracajú objekt typu Future, tento objekt slúži ako akási reprezentácia hodnoty spočítanej v budúcnosti. Na Future vieme pomocou metódy then navesiť callback, ktorý sa zavolá v momente, ako bude aktuálna hodnota k dispozícii.

void main() {
  var future = ajax();
  var futureAlert = future.then((result) => "Success is performed by $result"));
  futureAlert.then((value) => alert(value));
}

Vo vyššie uvedenom príklade sme do premennej future priradili výsledok asynchrónnej operácie ajax(). Následne sme cez future.then navesili callback, ktorý vezme spočítanú hodnotu result a vráti ju vo forme stringu (použité string interpolation). Všetko vrátené z callbacku vnútri then handlera sa považuje za ďalšiu Future, v našom prípade futureAlert. Na futureAlert navesíme jednoduchý then handler, ktorý alertne hodnotu.

Pre prípad, že nastane počas vyhodnocovania Future nejaká chyba, môžeme ju zachytiť pomocou catchError. Ak chceme zaregistrovať funkciu, ktorá sa vykoná v prípade chyby, no aj v prípade úspechu, môžeme použiť whenComplete.

void main() {
  var future = ajax();
  future.then((value) => handleValue(value))
        .catchError((error) => handleError()))
        .whenComplete(() => alert("This is like finally!");

}

Použitie pripomína klasický trycatchfinally blok a tak ho treba aj chápať – ako spôsob zápisu asynchrónneho obsluhovania výnimiek.

Poďme sa teraz pozrieť, ako pomocou Futures môžeme zjednodušiť náš pôvodný kód.

void main() {
  ajaxA().then((resultA) => ajaxB())
         .then((resultB) => ajaxC())
         .then((resultC) => handleC(resultC))
         .catchError((error) => handleError())

Pri pozornejšom zamyslení sa nie je úplne jasné, prečo by mal uvedený kód fungovať tak, ako by sme chceli. Volanie ajaxB() nevracia hodnotu, ale Future a podla toho, čo sme si povedali, by táto Future mala byť obalená v ďalšej Future, ktorú vráti volanie then.

Toto je našťastie vyriešené šikovne. Metóda Future then(onValue(T value)) po zavolaní vráti Future f, ktorá bude ukončená s výsledkom volania callbacku onValue. V prípade, že onValue vráti Future f2, f je zreťazená s f2, čo znamená, že f je ukončená až v momente, kedy je ukončená f2 a to s rovnakým výsledkom ako f2. Toto nám umožňuje reťaziť Futures tak, ako v príklade.

Na poradí nezáleží.

Predstavme si inú situáciu. Náš kód potrebuje vykonať viacero asynchrónnych volaní, ktoré sú na sebe úplne nezávislé, no nemôžeme pokračovať skôr, ako budú dokončené. Ako efektívne vyriešiť toto?

void main() {
  Future.wait([ajaxA(), ajaxB(), ajaxC()])
        .then((values) => alert("A: ${values[0]}, B: ${values[1]}, C: ${values[2]}"))
        .catchError((error) => handleError());
}

Statická metóda Future.wait očakáva List futures a výsledkom je Future, ktorá je ukončená v momente, kedy je ukončený posledný prvok vo futures. Výsledkom je List hodnôt s ktorými jednotlivé prvky futures skončili.

Chceme vlastné.

Naučili sme sa používať Futures, vyrábať ich je ešte jednoduchšie. Stačí využiť služby predpripravenej triedy Completer.

Future ajaxA() {
  var completer = new Completer();
  doSomeAjaxRequest(onSuccess: (result) {
    completer.complete(result);
  }, onError: () {
    completer.completeError(new Exception("Error!"))
  });

  return completer.future;
}

Completer vlastní property future, ktorú z funkcie ajaxA vraciame. V momente, kedy doSomeAjaxRequest (ne)úspešne dobehne, ukončí Completer future (ne)úspešne pomocou metódy complete , respektíve completeError.

Feedback prosím!

Nájdite si prosím chvíľu času na ohodnotenie tohto článku.

Zdroje

Pri písaní článku som čerpal z viacerých hodnotných zdrojov, nižšie ich nájdete zoradené podľa užitočnosti, zaujímavosti a aktuálnosti.

S kamošmi som založil VacuumLabs, špecializujeme sa na webové aplikácie. Momentálne frčíme na Pythone a pokukujeme po Darte. Nikdy neodmietam pozvanie na dobré české pivo.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 6

Přehled komentářů

Pavel Dvořák Trochu kritiky
Martin Hassman Re: Trochu kritiky
Pavel Dvořák Re: Trochu kritiky
apir Re: Trochu kritiky
blizz futures vs. await
crossborne Návrhový vzor "promise"
Zdroj: https://www.zdrojak.cz/?p=9969