Обробка відповіді $ http у сервісі


233

Нещодавно я опублікував детальний опис проблеми, з яким я стикаюся тут, на SO. Оскільки я не зміг надіслати фактичний $httpзапит, я використав тайм-аут, щоб імітувати асинхронну поведінку. Прив’язка даних з моєї моделі для перегляду працює правильно, за допомогою @Gloopy

Тепер, коли я використовую $httpзамість $timeout(тестується локально), я міг побачити, що асинхронний запит був успішним і dataнаповнений відповіддю json у моїй службі. Але, мій погляд, не оновлюється.

оновлено Plunkr тут

Відповіді:


419

Ось плагін, який виконує те, що ви хочете: http://plnkr.co/edit/TTlbSv?p=preview

Ідея полягає в тому, що ви працюєте з обіцянками безпосередньо та їх функціями "тоді" для маніпулювання та доступу до асинхронно повернених відповідей.

app.factory('myService', function($http) {
  var myService = {
    async: function() {
      // $http returns a promise, which has a then function, which also returns a promise
      var promise = $http.get('test.json').then(function (response) {
        // The then function here is an opportunity to modify the response
        console.log(response);
        // The return value gets picked up by the then in the controller.
        return response.data;
      });
      // Return the promise to the controller
      return promise;
    }
  };
  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  // Call the async method and then do stuff with what is returned inside our own then function
  myService.async().then(function(d) {
    $scope.data = d;
  });
});

Ось трохи складніша версія, яка кешує запит, тому ви робите це лише перший раз ( http://plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview ):

app.factory('myService', function($http) {
  var promise;
  var myService = {
    async: function() {
      if ( !promise ) {
        // $http returns a promise, which has a then function, which also returns a promise
        promise = $http.get('test.json').then(function (response) {
          // The then function here is an opportunity to modify the response
          console.log(response);
          // The return value gets picked up by the then in the controller.
          return response.data;
        });
      }
      // Return the promise to the controller
      return promise;
    }
  };
  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  $scope.clearData = function() {
    $scope.data = {};
  };
  $scope.getData = function() {
    // Call the async method and then do stuff with what is returned inside our own then function
    myService.async().then(function(d) {
      $scope.data = d;
    });
  };
});

13
Чи є ще спосіб викликати методи успіху та помилки в контролері після перехоплення послуги then?
andyczerwonka

2
@ PeteBD Якщо я хочу зателефонувати myService.async()кілька разів з різних контролерів, як би ви організували службу так, щоб це було $http.get()зроблено лише для першого запиту, а всі наступні запити просто повертають локальний масив об'єктів, який встановлюється під час першого дзвінка myService.async(). Іншими словами, я хочу уникати декількох, непотрібних запитів до служби JSON, коли насправді мені потрібно зробити лише один.
GFoley83

5
@ GFoley83 - ось вам: plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview . Якщо ви подивитесь на консоль, ви побачите, що запит робиться лише один раз.
Піт Б.Д.

3
@ PeteBD Я думаю, ви також можете використовувати $scope.data = myService.async()безпосередньо в контролері.
Джуліан

2
@ Blowsie- я оновив плавки. Ось оригінал (оновлено до 1.2RC3): plnkr.co/edit/3Nwxxk?p=preview Ось один із сервісів: plnkr.co/edit/a993Mn?p=preview
Pete BD

82

Нехай це буде просто. Це так само просто

  1. Повернення promiseу вашій службі (не потрібно використовувати thenв сервісі)
  2. Використовуйте thenу своєму контролері

Демо http://plnkr.co/edit/cbdG5p?p=preview

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

app.factory('myService', function($http) {
  return {
    async: function() {
      return $http.get('test.json');  //1. this returns promise
    }
  };
});

app.controller('MainCtrl', function( myService,$scope) {
  myService.async().then(function(d) { //2. so you can use .then()
    $scope.data = d;
  });
});

У вашому посиланні це app.factory, а у вашому коді - це app.service. Це передбачається app.factoryв цьому випадку.
Re Captcha

1
також працює програма app.service. Також - це для мене виглядає як найелегантніше рішення. Я щось пропускаю?
user1679130

1
Схоже, щоразу, коли у мене виникає кутова проблема, @allenhwkim має відповідь! (3-й раз цього тижня - чудовий компонент ng-map btw)
Ярін

я просто хочу знати , як поставити успіх і помилки тут з status_code
Ануй

58

Оскільки він асинхронний, то $scopeотримання даних до завершення виклику Ajax завершено.

Ви можете використовувати $qу своїй службі, щоб створити promiseта повернути його контролеру, а контролер отримати результат під час then()виклику проти promise.

До ваших послуг,

app.factory('myService', function($http, $q) {
  var deffered = $q.defer();
  var data = [];  
  var myService = {};

  myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data = d;
      console.log(d);
      deffered.resolve();
    });
    return deffered.promise;
  };
  myService.data = function() { return data; };

  return myService;
});

Потім у своєму контролері:

app.controller('MainCtrl', function( myService,$scope) {
  myService.async().then(function() {
    $scope.data = myService.data();
  });
});

2
+1 мені подобається цей найкращий, оскільки він більше OO, ніж інші. Однак немає ніяких підстав не робити цього this.async = function() {і this.getData = function() {return data}? Я сподіваюся, ви отримаєте те, що я маю на увазі
велосипед

@bicycle Я хотів це так само, але це не вийде, оскільки обіцянку треба вирішити до кінця. Якщо ви цього не зробите та не спробуєте отримати доступ до нього, як зазвичай, ви отримаєте помилку посилання під час доступу до внутрішніх даних. Сподіваюся, це має сенс?
користувач6123723

Якщо я правильно розумію, потрібно додати deffered = $q.defer()всередині myService.async, якщо я хочу зателефонувати на myService.async () два і більше разів
demas

1
Цей приклад - класичний відкладений антидіаграма . Не потрібно складати обіцянку, $q.deferоскільки $httpслужба вже повертає обіцянку. Повернута обіцянка буде висіти, якщо $httpповернення буде помилкою. Крім того, методи .successта .errorметоди застаріли і були вилучені з AngularJS 1.6 .
georgeawg

23

tosh shimayama має рішення, але ви можете багато спростити, якщо використовувати факт, що $ http повертає обіцянки і що обіцянки можуть повернути значення:

app.factory('myService', function($http, $q) {
  myService.async = function() {
    return $http.get('test.json')
    .then(function (response) {
      var data = reponse.data;
      console.log(data);
      return data;
    });
  };

  return myService;
});

app.controller('MainCtrl', function( myService,$scope) {
  $scope.asyncData = myService.async();
  $scope.$watch('asyncData', function(asyncData) {
    if(angular.isDefined(asyncData)) {
      // Do something with the returned data, angular handle promises fine, you don't have to reassign the value to the scope if you just want to use it with angular directives
    }
  });

});

Невелика демонстрація в coffeescript: http://plunker.no.de/edit/ksnErx?live=preview

Ваш plunker оновлений моїм методом: http://plnkr.co/edit/mwSZGK?p=preview


Я спробую далі по вашому підходу. Але мені подобається фіксувати результат у сервісі замість повернення. Дивіться питання, пов’язане з цим, тут stackoverflow.com/questions/12504747/… . Мені подобається обробляти дані, повернені $ http різними способами в контролері. ще раз дякую за вашу допомогу.
bsr

ви можете користуватися обіцянками в сервісах, якщо вам не подобається $ watch, ви можете робити «promise.then (функція (дані) {service.data = data;}, onErrorCallback); `
Guillaume86

Я додав плункер, розщеплений у вас
Guillaume86,

1
Ви також можете використовувати $ range. $ emit від служби та $ range. $ on на ctrl, щоб повідомити контролеру, що дані повернулися, але я не бачу вигоди
Guillaume86,

7

Набагато кращий спосіб, на мою думку, був би такий:

Сервіс:

app.service('FruitsManager',function($q){

    function getAllFruits(){
        var deferred = $q.defer();

        ...

        // somewhere here use: deferred.resolve(awesomeFruits);

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

А в контролері ви можете просто використовувати:

$scope.fruits = FruitsManager.getAllFruits();

Кутовий автоматично поставить вирішене значення awesomeFruitsв $scope.fruits.


4
deferred.resolve ()? Будьте точнішими, будь ласка, а де $ http дзвінок? Крім того, чому ви повертаєте об'єкт у службі .service?

6

У мене була така ж проблема, але коли я займався серфінгом в Інтернеті, я зрозумів, що $ http повертається за замовчуванням обіцянку, тоді я міг використовувати це з "тоді" після повернення "даних". подивіться на код:

 app.service('myService', function($http) {
       this.getData = function(){
         var myResponseData = $http.get('test.json').then(function (response) {
            console.log(response);.
            return response.data;
          });
         return myResponseData;

       }
});    
 app.controller('MainCtrl', function( myService, $scope) {
      // Call the getData and set the response "data" in your scope.  
      myService.getData.then(function(myReponseData) {
        $scope.data = myReponseData;
      });
 });

4

Прив'язуючи інтерфейс користувача до свого масиву, ви хочете переконатися, що ви оновите цей самий масив безпосередньо, встановивши довжину в 0 і натиснувши дані в масив.

Замість цього (який встановлює іншу посилання на масив, про dataякий ваш інтерфейс не знає):

 myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data = d;
    });
  };

спробуйте це:

 myService.async = function() {
    $http.get('test.json')
    .success(function (d) {
      data.length = 0;
      for(var i = 0; i < d.length; i++){
        data.push(d[i]);
      }
    });
  };

Ось загадка, яка показує різницю між встановленням нового масиву проти випорожнення та додаванням до існуючого. Я не міг змусити ваш plnkr працювати, але, сподіваюся, це працює для вас!


це не спрацювало. У журналі консолі я міг бачити, як d оновлено належним чином при успішному зворотному дзвінку, але не дані. Можливо, функція вже виконується.
bsr

Цей метод безумовно повинен працювати, можливо, він має щось спільне з типом даних d, який не є масивом (наприклад, на asp.net вам потрібно буде отримати доступ до DD для масиву). Дивіться цей plnkr для прикладу про помилку натискання рядка в масив: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)також буде працювати. Коли метад буде наданий методу copy (), він спочатку видалить елементи пункту призначення, а потім скопіює нові з джерела.
Марк Райкок

4

У зв’язку з цим я пережив подібну проблему, але не з отриманням чи публікацією, зробленим Angular, а з розширенням, зробленим третьою стороною (у моєму випадку розширення Chrome).
Проблема, з якою я стикався, полягає в тому, що розширення Chrome не повернеться, then()тому я не зміг зробити це так, як у рішенні вище, але результат все-таки асинхронний.
Тому моє рішення - створити сервіс і перейти до зворотного дзвінка

app.service('cookieInfoService', function() {
    this.getInfo = function(callback) {
        var model = {};
        chrome.cookies.get({url:serverUrl, name:'userId'}, function (response) {
            model.response= response;
            callback(model);
        });
    };
});

Потім у мого контролера

app.controller("MyCtrl", function ($scope, cookieInfoService) {
    cookieInfoService.getInfo(function (info) {
        console.log(info);
    });
});

Сподіваюся, що це може допомогти іншим отримати те саме питання.


4

Я читав http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ [AngularJS дозволяє нам впорядкувати нашу логіку контролера, розміщуючи обіцянку безпосередньо в області застосування, а не вручну передаючи рішення значення для успішного зворотного виклику.]

так просто і зручно :)

var app = angular.module('myApp', []);
            app.factory('Data', function($http,$q) {
                return {
                    getData : function(){
                        var deferred = $q.defer();
                        var promise = $http.get('./largeLoad').success(function (response) {
                            deferred.resolve(response);
                        });
                        // Return the promise to the controller
                        return deferred.promise; 
                    }
                }
            });
            app.controller('FetchCtrl',function($scope,Data){
                $scope.items = Data.getData();
            });

Сподіваюся, що це допоможе


не працює. повернене значення defrred.promiseне є функцією.
Юрген Пол

@PineappleUndertheSea, чому це потрібно для функції? Це об’єкт обіцянки.
Шев

@PineappleUndertheSea Ви мали на увазі використовувати відкладені, а не відкладені?
Деррік

2
Як PeteBD зазначив, ця форма $scope.items = Data.getData(); застаріла в Anglular
шикарна

2

Мені дуже не подобається той факт, що через "обіцяний" спосіб виконання дій споживач послуги, яка використовує $ http, повинен "знати" про те, як розпакувати відповідь.

Я просто хочу щось зателефонувати та отримати дані, схожі на старий $scope.items = Data.getData();спосіб, який зараз застарілий .

Я деякий час намагався і не придумав ідеального рішення, але ось мій найкращий знімок ( Plunker ). Можливо, комусь це стане в нагоді.

app.factory('myService', function($http) {
  var _data;  // cache data rather than promise
  var myService = {};

  myService.getData = function(obj) { 
    if(!_data) {
      $http.get('test.json').then(function(result){
        _data = result.data;
        console.log(_data);  // prove that it executes once
        angular.extend(obj, _data);
      }); 
    } else {  
      angular.extend(obj, _data);
    }
  };

  return myService;
}); 

Потім контролер:

app.controller('MainCtrl', function( myService,$scope) {
  $scope.clearData = function() {
    $scope.data = Object.create(null);
  };
  $scope.getData = function() {
    $scope.clearData();  // also important: need to prepare input to getData as an object
    myService.getData($scope.data); // **important bit** pass in object you want to augment
  };
});

Вади, які я вже можу помітити, є

  • Ви повинні передати об’єкт, до якого потрібно додати дані , що не є інтуїтивно зрозумілим або поширеним шаблоном у Angular
  • getDataможе приймати objпараметр лише у вигляді об’єкта (хоча він також може приймати масив), що не буде проблемою для багатьох додатків, але це боляче обмеження
  • Ви повинні підготувати об'єкт введення $scope.dataз , = {}щоб зробити його об'єктом ( по суті , що $scope.clearData()робить вище), або = []для масиву, або він не буде працювати (ми вже маючи припустити , що - то про те , що гряде дані). Я намагався зробити цей підготовчий крок IN getData, але не пощастило.

Тим не менш, він надає схему, яка видаляє контрольну панель котла "обіймати обіцянку" і може бути корисна у тих випадках, коли ви хочете використовувати певні дані, отримані з $ http, більше ніж в одному місці, зберігаючи її ДУХО.


1

Що стосується кешування відповіді в сервісі, ось ще одна версія, яка здається більш прямою, ніж те, що я бачив досі:

App.factory('dataStorage', function($http) {
     var dataStorage;//storage for cache

     return (function() {
         // if dataStorage exists returned cached version
        return dataStorage = dataStorage || $http({
      url: 'your.json',
      method: 'GET',
      cache: true
    }).then(function (response) {

              console.log('if storage don\'t exist : ' + response);

              return response;
            });

    })();

});

ця послуга повертає або кешовані дані, або $http.get;

 dataStorage.then(function(data) {
     $scope.data = data;
 },function(e){
    console.log('err: ' + e);
 });

0

Спробуйте скористатися наведеним нижче Кодексом

Ви можете розділити контролер (PageCtrl) та сервіс (dataService)

'use strict';
(function () {
    angular.module('myApp')
        .controller('pageContl', ['$scope', 'dataService', PageContl])
        .service('dataService', ['$q', '$http', DataService]);
    function DataService($q, $http){
        this.$q = $q;
        this.$http = $http;
        //... blob blob 
    }
    DataService.prototype = {
        getSearchData: function () {
            var deferred = this.$q.defer(); //initiating promise
            this.$http({
                method: 'POST',//GET
                url: 'test.json',
                headers: { 'Content-Type': 'application/json' }
            }).then(function(result) {
                deferred.resolve(result.data);
            },function (error) {
                deferred.reject(error);
            });
            return deferred.promise;
        },
        getABCDATA: function () {

        }
    };
    function PageContl($scope, dataService) {
        this.$scope = $scope;
        this.dataService = dataService; //injecting service Dependency in ctrl
        this.pageData = {}; //or [];
    }
    PageContl.prototype = {
         searchData: function () {
             var self = this; //we can't access 'this' of parent fn from callback or inner function, that's why assigning in temp variable
             this.dataService.getSearchData().then(function (data) {
                 self.searchData = data;
             });
         }
    }
}());

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.