Scott Tiger Tech Blog

Blog technologiczny firmy Scott Tiger S.A.

Archiwum dla Maj, 2014

JavaScript: Obietnice (ang. Promises)

Autor: Piotr Karpiuk o 26. maja 2014

JavaScript jest zasadniczo jednowątkowy, a wszelkie operacje I/O są wykonywane asynchronicznie. W rezultacie nasz kod wymagający kontaktu ze światem zewnętrznym staje się skomplikowany, zwłaszcza gdy uwzględnimy jeszcze obsługę błędów. Zawarty w specyfikacji EcmaScript 6 mechanizm obietnic (zaimplementowany w najnowszej wersji wiodących przeglądarek) pozwala pisać kod asynchroniczny w stylu bardziej synchronicznym.

Obietnica (ang. promise) w postaci obiektu klasy Promise reprezentuje wynik operacji asynchronicznej (wartość zwracaną w przypadku sukcesu lub błąd w razie porażki), który może nie być osiągalny na razie, ale z czasem będzie.

Obietnice możemy składać (ang. chain) ze sobą wykonując zestaw operacji asynchronicznych jedna za drugą (szeregowo) lub równolegle — w tym drugim przypadku będziemy mogli podjąć jakąś aktywność po zakończeniu wszystkich zleconych operacji. W takich łańcuchach obietnic błędy są propagowane, więc możemy na samym końcu łańcucha zdefiniować funkcję przechwytującą wszystkie możliwe błędy.

Wyobraźmy sobie kod wyświetlający powieść, przy czym poszczególne rozdziały ładowane są osobno po kolei, a najpierw pobieramy plik z URLami rozdziałów:

try {
  var story = getJSONSync('story.json');
  addHtmlToPage(story.heading);

  story.chapterUrls.forEach(function(chapterUrl) {
    var chapter = getJSONSync(chapterUrl);
    addHtmlToPage(chapter.html);
  });

  addTextToPage("All done");
}
catch (err) {
  addTextToPage("Argh, broken: " + err.message);
}

document.querySelector('.spinner').style.display = 'none';

Wszystko wygląda tu pięknie z dokładnością do tego, że synchroniczne operacje I/O blokują całą przeglądarkę. Implementacja tego z wykorzystaniem asynchronicznego I/O bardzo by nam ten kod zagmatwała.

Dzięki użyciu obietnic asynchroniczny kod może wyglądać następująco:

getJSON('story.json').then(function(story) {
  addHtmlToPage(story.heading);
  return Promise.all(
    story.chapterUrls.map(getJSON)
  );
}).then(function(chapters) {
  chapters.forEach(function(chapter) {
    addHtmlToPage(chapter.html);
  });
  addTextToPage("All done");
}).catch(function(err) {
  addTextToPage("Argh, broken: " + err.message);
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
});

Dodatkowym zyskiem widocznym na powyższym listingu (Promise.all()) jest równoczesne wykonywanie się asynchronicznych operacji wejścia/wyjścia pobierania rozdziałów. Gdy wszystkie zostaną załadowane (funkcja then()), możemy je wyświetlić. Błędy są wychwytywane za pomocą funkcji catch().

Przydatne artykuły:

Napisany w HTML5, JavaScript | Brak komentarzy »