กำลังประมวลผลการตอบสนอง $ 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 หากคุณดูที่คอนโซลคุณจะเห็นว่าคำขอนั้นทำเพียงครั้งเดียว
Pete BD

3
@ PetBD ฉันคิดว่าคุณสามารถใช้งาน$scope.data = myService.async()ได้โดยตรงในคอนโทรลเลอร์
Julian

2
@ Blowsie- ฉันได้อัพเดต Plunks แล้ว นี่คือต้นฉบับ (อัปเดตเป็น 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)
Yarin

ฉันแค่อยากรู้ว่าจะประสบความสำเร็จและผิดพลาดได้อย่างไรด้วย status_code
Anuj

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}? ฉันหวังว่าคุณจะได้รับสิ่งที่ฉันหมายถึง
จักรยาน

@ จักรยานฉันต้องการมันในแบบเดียวกัน แต่มันไม่ทำงานเพราะสัญญาต้องได้รับการแก้ไขตลอดทาง หากคุณไม่ทำและพยายามเข้าถึงตามปกติคุณจะได้รับข้อผิดพลาดอ้างอิงเมื่อเข้าถึงข้อมูลภายใน หวังว่ามันสมเหตุสมผล
user6123723

ถ้าฉันเข้าใจถูกต้องมีความจำเป็นต้องเพิ่มdeffered = $q.defer()ภายใน myService.async ถ้าฉันต้องการโทร myService.async () สองครั้งขึ้นไป
เดโม

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

ผู้ติดตามของคุณอัปเดตด้วยวิธีของฉัน: http://plnkr.co/edit/mwSZGK?p=preview


ฉันจะลองต่อไปตามแนวทางของคุณ แต่ฉันชอบจับผลลัพธ์ในบริการแทนที่จะกลับมา ดูคำถามที่เกี่ยวข้องกับที่นี่stackoverflow.com/questions/12504747/... ฉันต้องการประมวลผลข้อมูลที่ส่งคืนโดย $ http ด้วยวิธีต่างๆในคอนโทรลเลอร์ ขอบคุณอีกครั้งสำหรับความช่วยเหลือของคุณ
bsr

คุณสามารถใช้สัญญาในการบริการถ้าคุณไม่ชอบ $ watch คุณสามารถทำ rompromise.then (ฟังก์ชั่น (ข้อมูล) {service.data = data;}, onErrorCallback); `
Guillaume86

ฉันเพิ่มพลั่วเกอร์แยกจากคุณ
Guillaume86

1
หรือคุณสามารถใช้ $ scope $ ปล่อยออกมาจากบริการและ $ scope $ 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 อยู่ที่ไหน ทำไมคุณถึงส่งคืนวัตถุใน. บริการ

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

เมื่อผูก UI กับอาเรย์ของคุณคุณจะต้องแน่ใจว่าคุณอัปเดตอาเรย์นั้นโดยตรงโดยการตั้งค่าความยาวเป็น 0 และผลักข้อมูลลงในอาเรย์

แทนที่จะ (ซึ่งตั้งค่าการอ้างอิงอาร์เรย์ที่แตกต่างกันไปdataซึ่ง UI ของคุณจะไม่ทราบเกี่ยวกับ):

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

นี่คือซอที่แสดงความแตกต่างระหว่างการตั้งค่าอาร์เรย์ใหม่กับการล้างและการเพิ่มเข้าไปในอาร์เรย์ที่มีอยู่ ฉันไม่สามารถทำงานของคุณได้ แต่หวังว่านี่จะเป็นประโยชน์สำหรับคุณ!


ที่ไม่ทำงาน ในบันทึกของคอนโซลฉันเห็น d มีการปรับปรุงอย่างถูกต้องในการติดต่อกลับสำเร็จ แต่ไม่ใช่ข้อมูล อาจเป็นฟังก์ชั่นที่ใช้งานแล้ว
bsr

วิธีนี้ควรใช้งานได้อย่างแน่นอนบางทีอาจมีบางอย่างที่เกี่ยวข้องกับชนิดข้อมูลของ d ที่ไม่ได้เป็นอาเรย์ (ใน asp.net คุณจะต้องเข้าถึง dd สำหรับอาเรย์ด้วย) ดู plnkr นี้สำหรับตัวอย่างการส่งสตริงไปยังอาร์เรย์โดยมีข้อผิดพลาด: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)ยังจะทำงาน เมื่อปลายทางถูกส่งไปยังวิธีการ copy () มันจะลบองค์ประกอบของปลายทางก่อนแล้วคัดลอกองค์ประกอบใหม่จากแหล่งที่มา
Mark Rajcok

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

ฉันได้อ่าน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ไม่ใช่ฟังก์ชัน
Jürgen Paul

@PineappleUndertheSea ทำไมมันจำเป็นต้องมีฟังก์ชั่น? มันเป็นวัตถุสัญญา
Chev

@PineappleUndertheSea คุณหมายถึงการใช้งานที่เลื่อนออกไปและไม่หักล้างหรือไม่
ปั้นจั่น

2
ในฐานะที่เป็น PeteBD ชี้ให้เห็นรูปแบบนี้$scope.items = Data.getData(); จะเลิกใน Anglular
poshest

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()เหนือกว่า) หรือ= []สำหรับอาร์เรย์หรือมันจะไม่ทำงาน (เราต้องสมมติบางอย่างเกี่ยวกับข้อมูลที่กำลังจะมา) ฉันพยายามทำขั้นตอนการเตรียมการนี้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;
             });
         }
    }
}());

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.