Обработка $ http ответа в сервисе

227

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

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

обновлено Plunkr здесь

Теги:
angular-http

12 ответов

393
Лучший ответ

Вот Plunk, который делает то, что вы хотите: http://plnkr.co/edit/TTlbSv?p=preview

Идея заключается в том, что вы напрямую работаете с promises и своими функциями "then", чтобы манипулировать и получать доступ к асинхронно возвращенным ответам.

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;
    });
  };
});
  • 0
    чтобы протестировать контроллер, вы хотите внедрить фиктивную версию "myService", которая имеет фиктивный метод async (), который возвращает объект с фиктивным методом then (), содержащим фиктивные данные, или есть лучший способ?
  • 0
    @JoeHanink: Это не совсем то же самое, поскольку он тестирует директиву, а не контроллер, но вы можете увидеть, что вы можете сделать здесь: github.com/angular-app/Samples/tree/master/1820EN_09_Code/ ...
Показать ещё 19 комментариев
75

Пусть это будет просто. Это так же просто, как

  • Верните promise в свою службу (нет необходимости использовать then в службе)
  • Используйте then в вашем контроллере

Demo. 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;
  });
});
  • 0
    В вашей ссылке это app.factory , а в вашем коде - app.service . Это должно быть app.factory в этом случае.
  • 1
    app.service тоже работает. Также - для меня это выглядит как самое элегантное решение. Я что-то пропустил?
Показать ещё 2 комментария
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 Мне больше нравится этот, так как он больше ОО, чем другие. Однако есть ли причина, по которой вы этого не делаете this.async = function() { и this.getData = function() {return data} ? Я надеюсь, вы понимаете, о чем я
  • 0
    @Bicycle Я хотел это так же, но это не сработает, потому что обещание должно быть выполнено полностью. Если вы этого не сделаете и попытаетесь получить к нему доступ, как обычно, вы получите справочную ошибку при доступе к внутренним данным. Надеюсь, это имеет смысл?
Показать ещё 2 комментария
24

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

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

Ваш плункер обновлен моим методом: http://plnkr.co/edit/mwSZGK?p=preview

  • 0
    Я попробую дальше по вашему подходу. Но мне нравится фиксировать результат в сервисе, а не возвращать. Смотрите вопрос, связанный с этим здесь stackoverflow.com/questions/12504747/… . Мне нравится обрабатывать данные, возвращаемые $ http, различными способами в контроллере. В очередной раз благодарим за помощь.
  • 0
    вы можете использовать обещания в сервисах, если вам не нравится $ watch, вы можете сделать «promise.then (function (data) {service.data = data;}, onErrorCallback);»
Показать ещё 2 комментария
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();

Angular автоматически помещает разрешенный awesomeFruits в $scope.fruits.

  • 4
    deferred.resolve ()? Если быть более точным, пожалуйста, а где $ http звонок? Кроме того, почему вы возвращаете объект в .service?
5

У меня была такая же проблема, но когда я занимался серфингом в Интернете, я понял, что $http возвратит по умолчанию обещание, после чего я смогу использовать его с "then" после возврата "данных". посмотрите код:

 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

Я читал 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();
            });

Надеемся на эту помощь

  • 0
    не работает возвращаемое значение defrred.promise не является функцией.
  • 0
    @PineappleUndertheSea, почему это должно быть функцией? Это обещанный объект.
Показать ещё 2 комментария
4

В связи с этим я столкнулся с подобной проблемой, но не с получением или записью, сделанной Angular, а с расширением, сделанным третьим лицом (в моем случае Chrome Extension).
Проблема, с которой я столкнулся, заключается в том, что расширение 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

При привязке пользовательского интерфейса к вашему массиву вы захотите убедиться, что вы сразу же обновите тот же массив, установив длину в 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 работать, но, надеюсь, это сработает для вас!

  • 0
    это не сработало. в журнале консоли я мог видеть, что d обновляется должным образом при успешном обратном вызове, но не данные. Может быть, функция уже выполнена.
  • 0
    Этот метод определенно должен работать, возможно, он как-то связан с тем, что тип данных d не является массивом (например, в asp.net вам потребуется доступ к dd для массива, например). Посмотрите этот plnkr для примера, вставляющего строку в массив при ошибке: plnkr.co/edit/7FuwlN?p=preview
Показать ещё 1 комментарий
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 в нескольких местах, сохраняя при этом DRY.

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 why assigning in temp variable
             this.dataService.getSearchData().then(function (data) {
                 self.searchData = data;
             });
         }
    }
}());

Ещё вопросы

Сообщество Overcoder
Наверх
Меню