Xử lý phản hồi $ http trong dịch vụ


233

Gần đây tôi đã đăng một mô tả chi tiết về vấn đề tôi đang gặp phải ở đây tại SO. Vì tôi không thể gửi $httpyêu cầu thực tế , tôi đã sử dụng thời gian chờ để mô phỏng hành vi không đồng bộ. Liên kết dữ liệu từ mô hình của tôi để xem hoạt động chính xác, với sự trợ giúp của @Gloopy

Bây giờ, khi tôi sử dụng $httpthay vì $timeout(được thử nghiệm cục bộ), tôi có thể thấy yêu cầu không đồng bộ đã thành công và datachứa đầy phản hồi json trong dịch vụ của tôi. Nhưng, quan điểm của tôi không cập nhật.

cập nhật Plunkr tại đây

Câu trả lời:


419

Đây là một Plunk thực hiện những gì bạn muốn: http://plnkr.co/edit/TTlbSv?p=preview

Ý tưởng là bạn làm việc với các lời hứa trực tiếp và các chức năng "sau đó" của chúng để thao tác và truy cập các phản hồi được trả lại không đồng bộ.

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;
  });
});

Đây là một phiên bản phức tạp hơn một chút lưu trữ yêu cầu để bạn chỉ thực hiện lần đầu tiên ( 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
Có cách nào để vẫn gọi các phương thức thành công và lỗi trong bộ điều khiển sau khi dịch vụ đã bị chặn thenkhông?
andyczerwonka

2
@PeteBD Nếu tôi muốn gọi myService.async()nhiều lần từ nhiều bộ điều khiển khác nhau, bạn sẽ tổ chức dịch vụ như thế nào để chỉ $http.get()yêu cầu đầu tiên và tất cả các yêu cầu tiếp theo chỉ trả về một mảng đối tượng cục bộ được đặt ở cuộc gọi đầu tiên myService.async(). Nói cách khác, tôi muốn tránh nhiều yêu cầu, không cần thiết đối với dịch vụ JSON, khi thực sự tôi chỉ cần thực hiện một yêu cầu.
GFoley83

5
@ GFoley83 - bạn vào đây: plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview . Nếu bạn nhìn vào bảng điều khiển, bạn sẽ thấy rằng yêu cầu chỉ được thực hiện một lần.
Pete BD

3
@PeteBD Tôi nghĩ bạn cũng có thể sử dụng $scope.data = myService.async()trực tiếp trong bộ điều khiển.
Julian

2
@ Blowsie- Tôi đã cập nhật Plunks. Đây là bản gốc (được cập nhật lên 1.2RC3): plnkr.co/edit/3Nwxxk?p=preview Đây là một dịch vụ sử dụng: plnkr.co/edit/a993Mn?p=preview
Pete BD

82

Hãy để nó đơn giản. Nó đơn giản như

  1. Quay trở lại promisedịch vụ của bạn (không cần sử dụng thendịch vụ)
  2. Sử dụng thentrong bộ điều khiển của bạn

Bản giới thiệu. 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;
  });
});

Trong liên kết của bạn, nó app.factoryvà mã của bạn app.service. Nó được cho là app.factorytrong trường hợp này.
Re Captcha

1
ứng dụng dịch vụ cũng vậy. Ngoài ra - điều này với tôi trông giống như giải pháp thanh lịch nhất. Tui bỏ lỡ điều gì vậy?
dùng1679130

1
Có vẻ như mỗi lần tôi gặp vấn đề về Angular @allenhwkim đều có câu trả lời! (Lần thứ 3 trong tuần này - thành phần ng-map tuyệt vời btw)
Yarin

tôi chỉ muốn biết làm thế nào để đặt thành công và lỗi ở đây với status_code
Anuj

58

Bởi vì nó không đồng bộ, $scopenên việc lấy dữ liệu trước khi cuộc gọi ajax hoàn tất.

Bạn có thể sử dụng $qtrong dịch vụ của mình để tạo promisevà trả lại cho bộ điều khiển và bộ điều khiển có được kết quả trong then()cuộc gọi promise.

Trong dịch vụ của bạn,

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;
});

Sau đó, trong bộ điều khiển của bạn:

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

2
+1 tôi thích cái này tốt nhất vì nó nhiều OO hơn những cái khác. Tuy nhiên, có lý do gì bạn không làm điều này this.async = function() {this.getData = function() {return data}? Tôi hy vọng bạn hiểu ý tôi
xe đạp

@b wheel Tôi muốn nó theo cùng một cách nhưng nó sẽ không hoạt động vì lời hứa phải được giải quyết theo mọi cách. Nếu bạn không và cố gắng truy cập nó như bình thường, bạn sẽ gặp lỗi tham chiếu khi truy cập dữ liệu nội bộ. Hy vọng nó có ý nghĩa?
dùng6123723

Nếu tôi hiểu chính xác, cần phải thêm deffered = $q.defer()vào bên trong myService.async nếu tôi muốn gọi myService.async () hai lần trở lên
demas

1
Ví dụ này là một mô hình chống trì hoãn cổ điển . Không cần thiết phải thực hiện lời hứa $q.defer$httpdịch vụ đã trả lại lời hứa. Lời hứa trả lại sẽ bị treo nếu $httptrả về lỗi. Ngoài ra, các phương thức .success.errorkhông được dùng nữa và đã bị xóa khỏi AngularJS 1.6 .
georgeawg

23

tosh shimayama có một giải pháp nhưng bạn có thể đơn giản hóa rất nhiều nếu bạn sử dụng thực tế là $ http trả lại lời hứa và lời hứa đó có thể trả về giá trị:

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
    }
  });

});

Một minh chứng nhỏ trong coffeescript: http://plunker.no.de/edit/ksnErx?live=preview

Plunker của bạn được cập nhật theo phương pháp của tôi: http://plnkr.co/edit/mwSZGK?p=preview


Tôi sẽ cố gắng hơn nữa theo cách tiếp cận của bạn. Nhưng, tôi thích nắm bắt kết quả trong dịch vụ thay vì quay lại. Xem câu hỏi liên quan đến vấn đề này tại đây stackoverflow.com/questions/12504747/ . Tôi muốn xử lý dữ liệu được trả về bởi $ http theo các cách khác nhau trong bộ điều khiển. Cảm ơn một lần nữa vì sự giúp đỡ của bạn.
bsr

bạn có thể sử dụng lời hứa trong các dịch vụ, nếu bạn không thích $ watch, bạn có thể thực hiện promise.then (function (data) {service.data = data;}, onErrorCallback); `
Guillaume86

Tôi đã thêm một plunker rẽ nhánh từ bạn
Guillaume86

1
ngoài ra, bạn có thể sử dụng $ scope. $ phát ra từ dịch vụ và $ scope. $ trên ctrl để cho bạn biết bộ điều khiển rằng dữ liệu đã được trả về nhưng tôi không thực sự thấy lợi ích
Guillaume86

7

Một cách tốt hơn nhiều tôi nghĩ sẽ là một cái gì đó như thế này:

Dịch vụ:

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

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

        ...

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

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

Và trong bộ điều khiển bạn chỉ cần sử dụng:

$scope.fruits = FruitsManager.getAllFruits();

Angular sẽ tự động đưa giải quyết awesomeFruitsvào $scope.fruits.


4
hoãn lại.resolve ()? Hãy chính xác hơn xin vui lòng và cuộc gọi $ http ở đâu? Ngoài ra tại sao bạn trả lại một đối tượng trong một dịch vụ.

6

Tôi cũng gặp vấn đề tương tự, nhưng khi tôi lướt web trên mạng, tôi hiểu rằng $ http trở lại theo mặc định là một lời hứa, sau đó tôi có thể sử dụng nó với "sau đó" sau khi trả lại "dữ liệu". nhìn vào mã:

 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

Khi liên kết UI với mảng của bạn, bạn sẽ muốn đảm bảo rằng bạn cập nhật trực tiếp cùng mảng đó bằng cách đặt độ dài thành 0 và đẩy dữ liệu vào mảng.

Thay vì điều này (thiết lập một tham chiếu mảng khác datamà UI của bạn sẽ không biết):

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

thử cái này:

 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]);
      }
    });
  };

Dưới đây là một câu đố cho thấy sự khác biệt giữa việc thiết lập một mảng mới so với làm trống và thêm vào một mảng hiện có. Tôi không thể làm cho plnkr của bạn hoạt động nhưng hy vọng điều này sẽ làm việc cho bạn!


Điều đó đã không làm việc. trong nhật ký giao diện điều khiển, tôi có thể thấy d được cập nhật đúng trong cuộc gọi lại thành công, nhưng không phải dữ liệu. Có thể là chức năng đã được thực hiện.
bsr

Phương pháp này chắc chắn sẽ hoạt động có lẽ nó có liên quan đến kiểu dữ liệu của d không phải là một mảng (trong asp.net bạn cần truy cập dd cho mảng chẳng hạn). Xem plnkr này để biết ví dụ đẩy một chuỗi vào mảng bị lỗi: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)cũng sẽ làm việc Khi một đích được cung cấp cho phương thức copy (), đầu tiên nó sẽ xóa các phần tử của đích và sau đó sao chép trong các phần tử mới từ nguồn.
Mark Rajcok

4

Liên quan đến vấn đề này, tôi đã trải qua một vấn đề tương tự, nhưng không phải là lấy hoặc đăng bởi Angular mà là một tiện ích mở rộng được tạo bởi bên thứ 3 (trong trường hợp của tôi là Tiện ích mở rộng Chrome).
Vấn đề mà tôi gặp phải là Tiện ích mở rộng Chrome sẽ không quay trở lại then()nên tôi không thể thực hiện theo cách trong giải pháp trên nhưng kết quả vẫn không đồng bộ.
Vì vậy, giải pháp của tôi là tạo ra một dịch vụ và tiến hành gọi lại

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

Sau đó, trong bộ điều khiển của tôi

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

Hy vọng điều này có thể giúp những người khác nhận được cùng một vấn đề.


4

Tôi đã đọc http://markdalgleish.com/2013/06/USE-promises-in-angularjs-view/ [AngularJS cho phép chúng tôi hợp lý hóa logic bộ điều khiển của mình bằng cách đặt lời hứa trực tiếp trên phạm vi, thay vì tự xử lý giải quyết giá trị trong một cuộc gọi lại thành công.]

thật đơn giản và tiện dụng :)

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();
            });

Hy vọng điều này giúp đỡ


không hoạt động. giá trị trả về của defrred.promisekhông phải là một hàm.
Jürgen Paul

@PinnutUndertheSea tại sao nó cần phải là một chức năng? Đó là một đối tượng hứa hẹn.
Chev

@PinnutUndertheSea có nghĩa là bạn sử dụng hoãn lại và không bị trì hoãn?
Derrick

2
Như PeteBD đã chỉ ra, hình thức $scope.items = Data.getData(); này không được chấp nhận trong Anglular
lâu nhất vào

2

Tôi thực sự không thích thực tế rằng, vì cách thực hiện "lời hứa", người tiêu dùng dịch vụ sử dụng $ http phải "biết" về cách giải nén phản hồi.

Tôi chỉ muốn gọi một cái gì đó và lấy dữ liệu ra, tương tự như $scope.items = Data.getData();cách cũ , hiện không được dùng nữa .

Tôi đã thử một lúc và không đưa ra được giải pháp hoàn hảo, nhưng đây là bức ảnh đẹp nhất của tôi ( Plunker ). Nó có thể hữu ích cho một ai đó.

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;
}); 

Sau đó, bộ điều khiển:

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
  };
});

Lỗ hổng tôi có thể phát hiện ra là

  • Bạn phải truyền vào đối tượng mà bạn muốn dữ liệu được thêm vào , đây không phải là mẫu trực quan hoặc phổ biến trong Angular
  • getDatachỉ có thể chấp nhận objtham số ở dạng đối tượng (mặc dù nó cũng có thể chấp nhận một mảng), điều này sẽ không phải là vấn đề đối với nhiều ứng dụng, nhưng đó là một hạn chế đau
  • Bạn cần phải chuẩn bị những đối tượng đầu vào $scope.datavới = {}để làm cho nó một đối tượng (thực chất là những gì $scope.clearData()làm ở trên), hoặc = []cho một mảng, hoặc nó sẽ không làm việc (chúng tôi đã phải thừa nhận điều gì đó về những dữ liệu đang đến). Tôi đã cố gắng thực hiện bước chuẩn bị này VÀO getData, nhưng không có may mắn.

Tuy nhiên, nó cung cấp một mẫu loại bỏ bản tóm tắt "hứa ​​hẹn không điều khiển" của bộ điều khiển và có thể hữu ích trong trường hợp khi bạn muốn sử dụng một số dữ liệu nhất định thu được từ $ http ở nhiều nơi trong khi vẫn giữ DRY.


1

Theo như bộ nhớ đệm, phản hồi trong dịch vụ có liên quan, đây là một phiên bản khác có vẻ thẳng hơn so với những gì tôi đã thấy cho đến nay:

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;
            });

    })();

});

dịch vụ này sẽ trả về dữ liệu được lưu trong bộ nhớ cache hoặc $http.get;

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

0

Vui lòng thử mã dưới đây

Bạn có thể tách bộ điều khiển (PageCtrl) và dịch vụ (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;
             });
         }
    }
}());

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.