Promise $q na przykładzie AngularJS

Załóżmy, że mamy następujący prosty kod z obsługą kontrolera MainCtrl i serwisu dataService.

Jak widać, dataService wstrzyknięty do kontrolera, dostarcza jedną funkcję zwracającą „hardkodowaną” tablicę obiektów items. W dalszej części zasymuluję pobieranie danych z serwera za pomocą opóźnienia uzyskanego przez wbudowany serwis $timeout.

(function() {

  var app = angular.module('plunker', []);

  app
    .controller('MainCtrl', MainCtrl)
    .factory('dataService', ['$q', '$timeout', dataService]);

  function MainCtrl(dataService) {
    var vm = this;
    vm.name = 'World';
    vm.items = dataService.getAllItems();
  }

  function dataService($q, $timeout) {
    return {
      getAllItems: getAllItems
    };

    function getAllItems() {
      var booksArray = [{
        'item': 1,
        'name': 'item 1',
      }, {
        'item': 2,
        'name': 'item 2',
      }, {
        'item': 3,
        'name': 'item 3',
      }, {
        'item': 4,
        'name': 'item 4',
      }];
      
      return booksArray;
    }
  }

})()

W widoku zawartość zmiennych obejrzymy za pomocą:

  <body ng-controller="MainCtrl as mc">
    <p>Hello {{mc.name}}!</p>
    {{ mc.items }}
  </body>
  
  

Symulacja pobierania danych z serwera

Każde pobieranie danych z serwera wiąże się z pewnym asynchronicznym opóźnieniem. W naszym przykładzie opóźnienie to uzyskuje poprzez użycie serwisu $timeout. Poniższy kod przedstawia modyfikację serwisu dataService:

  function dataService($q, $timeout) {
    return {
      getAllItems: getAllItems
    };

    function getAllItems() {
      // ...
      // tablica obiektów itemsArray
      // ... 

      $timeout(function() {
        console.log('dump array items: ', itemsArray);
        return itemsArray;
      }, 1000);
    }
  }
  
  

W rezultacie w widoku otrzymamy wynik nieco uboższy – {{mc.items}} nie zwróci ciągu JSON, ponieważ w momencie renderowania widoku tablica ta była pusta.

Dlaczego? Mówiąc najprościej, cały program wykonał się w ułamku sekundy i „nie poczekał” za dane pobierane asynchroniczne z backendu, których pobranie wymaga pewnego czasu (w tym przypadku czasu stałego 1s). W konsoli (np. Firebug) można zauważyć, że dump array items pojawi się dopiero 1s po zakończeniu wykonywania całego programu.

Jak zapewnić, że dane zostaną pobrane i zaprezentowane w widoku niezależnie od czasu ich pobierania?

Są 2 sposoby. Pobieranie synchroniczne (blokujące) lub obietnice (promises). My zajmiemy się tym tym drugim.

Obietnica dostarczenia danych z serwera

Zmodyfikujmy kod dodając obsługę promise $q:

(function() {

  var app = angular.module('plunker', []);

  app
    .controller('MainCtrl', MainCtrl)
    .factory('dataService', ['$q', '$timeout', dataService]);

  function MainCtrl(dataService) {
    var vm = this;
    vm.name = 'World';

    console.log('check 1');

    dataService
      .getAllItems()
      .then(
        getItemsSuccess,
        getItemsFailure
      );

    console.log('check 2');

    // getAllItems() success handler
    function getItemsSuccess(items) {
      vm.items = items;
      console.log('success - now me!');
    }

    // getAllItems() error handler
    function getItemsFailure(error) {
      console.log('failure - now me!');
      console.log(error);
    }

  }

  function dataService($q, $timeout) {
    return {
      getAllItems: getAllItems
    };

    function getAllItems() {
      // ...
      // tablica obiektów itemsArray
      // ...

      var deferred = $q.defer();

      $timeout(function() {
        var successful = true;
        if (successful) {
          deferred.resolve(itemsArray);
        } else {
          deferred.reject('Error retriving Items');
        }
      }, 1000);

      return deferred.promise;
    }
  }

})()

W dataService został utworzony obiekt deferred, który gwarantuje (obiecuje) zwrócenie wyniku (jakiegokolwiek) w jednym z dwóch przypadków: rozwiązania (resolve) lub odrzucenia (reject). Niezależnie, po jakim czasie nastąpi resolve lub reject, deferred zapewni, że dataService.getAllItems() zwróci rezultat do dalszego obsłużenia w kontrolerze.

Obsługa w kontrolerze sprowadza się do wywołania metody .then() obsługującej 2 metody anonimowe lub – tak jak w naszym przypadku – getItemsSuccess() wywoływaną w przypadku powodzenia i getItemsFailure() wywoływaną w przypadku wystąpienia błędu (wywołanie .reject można zasymulować ustawieniem zmiennej succcessful = false).

 

 

 

 

 

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *