Memproses respons $ http dalam layanan


233

Saya baru-baru diposting penjelasan rinci tentang masalah yang saya hadapi di sini di SO. Karena saya tidak dapat mengirim $httppermintaan yang sebenarnya , saya menggunakan batas waktu untuk mensimulasikan perilaku asinkron. Pengikatan data dari model saya untuk melihat berfungsi dengan benar, dengan bantuan @Gloopy

Sekarang, ketika saya menggunakan $httpalih-alih $timeout(diuji secara lokal), saya bisa melihat permintaan asinkron berhasil dan datadipenuhi dengan respons json di layanan saya. Tapi, pandangan saya tidak memperbarui.

memperbarui Plunkr di sini

Jawaban:


419

Berikut adalah Plunk yang melakukan apa yang Anda inginkan: http://plnkr.co/edit/TTlbSv?p=preview

Idenya adalah Anda bekerja dengan janji secara langsung dan fungsi "lalu" mereka untuk memanipulasi dan mengakses respons yang dikembalikan secara tidak sinkron.

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

Ini adalah versi yang sedikit lebih rumit yang menyimpan permintaan sehingga Anda hanya membuatnya pertama kali ( 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
Apakah ada cara untuk tetap memanggil metode keberhasilan dan kesalahan di controller setelah layanan dicegat then?
andyczerwonka

2
@PeteBD Jika saya ingin menelepon myService.async()beberapa kali dari berbagai pengontrol, bagaimana Anda mengatur layanan sehingga hanya melakukan $http.get()permintaan pertama, dan semua permintaan berikutnya hanya mengembalikan array objek lokal yang akan diatur pada panggilan pertama ke myService.async(). Dengan kata lain, saya ingin menghindari beberapa permintaan yang tidak perlu ke layanan JSON, padahal sebenarnya saya hanya perlu membuat satu.
GFoley83

5
@ GFoley83 - ini dia: plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview . Jika Anda melihat konsol, Anda akan melihat bahwa permintaan hanya dilakukan satu kali.
Pete BD

3
@ PeteBD Saya pikir Anda juga dapat menggunakan $scope.data = myService.async()langsung di controller.
Julian

2
@ Blowsie- Saya telah memperbarui Plunks. Inilah yang asli (diperbarui ke 1.2RC3): plnkr.co/edit/3Nwxxk?p=preview Ini adalah layanan yang menggunakan: plnkr.co/edit/a993Mn?p=preview
Pete BD

82

Biarkan itu sederhana. Sesederhana itu

  1. Kembali promisedalam layanan Anda (tidak perlu digunakan thendalam layanan)
  2. Gunakan thendi controller Anda

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

Di tautan Anda, itu app.factory, dan dalam kode Anda itu app.service. Seharusnya app.factorydalam kasus ini.
Re Captcha

1
pekerjaan app.service juga. Juga - bagi saya ini terlihat seperti solusi paling elegan. Apakah saya melewatkan sesuatu?
user1679130

1
Sepertinya setiap kali saya punya masalah Angular @allenhwkim punya jawabannya! (Ketiga kalinya minggu ini komponen peta-besar btw)
Yarin

saya hanya ingin tahu bagaimana cara menempatkan sukses dan kesalahan di sini dengan status_code
Anuj

58

Karena asinkron, $scopeia mendapatkan data sebelum panggilan ajax selesai.

Anda dapat menggunakan $qdalam layanan Anda untuk membuat promisedan mengembalikannya ke pengontrol, dan pengontrol mendapatkan hasilnya saat then()dihubungi promise.

Dalam layanan Anda,

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

Kemudian, di controller Anda:

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

2
+1 saya paling suka yang ini karena lebih banyak OO daripada yang lain. Namun apakah ada alasan Anda tidak melakukan ini this.async = function() {dan this.getData = function() {return data}? Saya harap Anda mengerti maksud saya
sepeda

@ Sepeda Saya menginginkannya dengan cara yang sama tetapi itu tidak akan berhasil karena janji harus diselesaikan sepanjang jalan. Jika tidak dan coba mengaksesnya seperti biasanya, Anda akan mendapatkan kesalahan referensi saat mengakses data internal. Semoga ini masuk akal?
user6123723

Jika saya mengerti dengan benar, saya perlu menambahkan deffered = $q.defer()di dalam myService.async jika saya ingin memanggil myService.async () dua atau lebih waktu
demo

1
Contoh ini adalah anti-pola klasik tangguhan . Tidak perlu membuat janji dengan $q.deferkarena $httplayanan sudah mengembalikan janji. Janji yang dikembalikan akan hang jika $httpmengembalikan kesalahan. Selain itu, .successdan .errormetode sudah usang dan telah dihapus dari AngularJS 1.6 .
georgeawg

23

tosh shimayama punya solusi tetapi Anda bisa menyederhanakan banyak hal jika menggunakan fakta bahwa $ http mengembalikan janji dan bahwa janji dapat mengembalikan nilai:

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

});

Demonstrasi kecil dalam coffeescript: http://plunker.no.de/edit/ksnErx?live=preview

Plunker Anda diperbarui dengan metode saya: http://plnkr.co/edit/mwSZGK?p=preview


Saya akan mencoba lebih jauh dalam pendekatan Anda. Tapi, saya suka menangkap hasilnya dalam layanan alih-alih kembali. Lihat pertanyaan terkait dengan ini di sini stackoverflow.com/questions/12504747/… . Saya suka memproses data yang dikembalikan oleh $ http dengan cara yang berbeda di controller. terima kasih lagi atas bantuan anda.
bsr

Anda dapat menggunakan janji dalam layanan, jika Anda tidak suka $ watch, Anda dapat melakukan ´promise.then (fungsi (data) {service.data = data;}, onErrorCallback); `
Guillaume86

Saya menambahkan plunker yang bercabang dari Anda
Guillaume86

1
atau Anda dapat menggunakan $ scope. $ emit dari layanan dan $ scope. $ pada ctrl untuk memberi tahu Anda controller bahwa data telah kembali tetapi saya tidak benar-benar melihat manfaatnya
Guillaume86

7

Cara yang jauh lebih baik menurut saya akan menjadi seperti ini:

Layanan:

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

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

        ...

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

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

Dan pada controller Anda cukup menggunakan:

$scope.fruits = FruitsManager.getAllFruits();

Sudut akan secara otomatis menempatkan diselesaikan awesomeFruitske dalam $scope.fruits.


4
deferred.resolve ()? Lebih tepatnya tolong dan di mana panggilan $ http? Juga mengapa Anda mengembalikan objek dalam layanan?

6

Saya memiliki masalah yang sama, tetapi ketika saya berselancar di internet saya mengerti bahwa $ http mengembalikan janji secara default, maka saya dapat menggunakannya dengan "lalu" setelah mengembalikan "data". lihat kode:

 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

Saat mengikat UI ke array Anda, Anda ingin memastikan Anda memperbarui array yang sama secara langsung dengan mengatur panjang ke 0 dan mendorong data ke dalam array.

Alih-alih ini (yang menetapkan referensi array berbeda datayang tidak akan diketahui oleh UI Anda):

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

coba ini:

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

Berikut adalah biola yang menunjukkan perbedaan antara pengaturan array baru vs pengosongan dan menambahkan ke yang sudah ada. Saya tidak bisa membuat plnkr Anda berfungsi, tapi semoga ini berhasil untuk Anda!


itu tidak berhasil. di log konsol, saya bisa melihat d diperbarui dengan benar dalam panggilan balik sukses, tetapi tidak data. Mungkin saja fungsinya sudah dijalankan.
bsr

Metode ini pasti bekerja mungkin itu ada hubungannya dengan tipe data d tidak menjadi array (di asp.net Anda perlu mengakses dd untuk array misalnya). Lihat plnkr ini sebagai contoh mendorong string ke dalam array karena kesalahan: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)juga akan bekerja. Ketika suatu tujuan disuplai ke metode salin (), itu pertama-tama akan menghapus elemen-elemen tujuan, dan kemudian menyalin yang baru dari sumber.
Mark Rajcok

4

Terkait dengan ini saya mengalami masalah yang sama, tetapi tidak dengan get atau post yang dibuat oleh Angular tetapi dengan ekstensi yang dibuat oleh pihak ke-3 (dalam kasus saya, Chrome Extension).
Masalah yang saya hadapi adalah bahwa Ekstensi Chrome tidak akan kembali then()sehingga saya tidak dapat melakukannya dengan cara di solusi di atas tetapi hasilnya masih Asynchronous.
Jadi solusi saya adalah membuat layanan dan melanjutkan panggilan balik

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

Kemudian di controller saya

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

Semoga ini bisa membantu orang lain mendapatkan masalah yang sama.


4

Saya telah membaca http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ [AngularJS memungkinkan kami untuk merampingkan logika pengontrol kami dengan menempatkan janji langsung pada ruang lingkup, daripada menyerahkan secara manual secara terselesaikan nilai dalam panggilan balik sukses.]

sangat sederhana dan praktis :)

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

Semoga bantuan ini


tidak bekerja nilai pengembalian defrred.promisebukan fungsi.
Jürgen Paul

@PineappleUndertheSea mengapa perlu fungsi? Itu adalah benda yang dijanjikan.
Chev

@PineappleUndertheSea maksud Anda menggunakan ditangguhkan dan tidak dihapus?
Derrick

2
Seperti yang ditunjukkan PeteBD, formulir $scope.items = Data.getData(); ini tidak digunakan lagi dalam Anglular
poshest

2

Saya benar-benar tidak menyukai kenyataan bahwa, karena cara "janji" dalam melakukan sesuatu, konsumen layanan yang menggunakan $ http harus "tahu" tentang cara membongkar tanggapan.

Saya hanya ingin memanggil sesuatu dan mengeluarkan data, mirip dengan $scope.items = Data.getData();cara lama , yang sekarang sudah usang .

Saya mencoba untuk sementara waktu dan tidak menghasilkan solusi yang sempurna, tapi inilah foto terbaik saya ( Plunker ). Mungkin bermanfaat bagi seseorang.

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

Kemudian controller:

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

Kekurangan yang sudah bisa saya temukan

  • Anda harus meneruskan objek yang Anda inginkan data ditambahkan , yang bukan pola intuitif atau umum di Angular
  • getDatahanya dapat menerima objparameter dalam bentuk objek (walaupun itu juga bisa menerima array), yang tidak akan menjadi masalah bagi banyak aplikasi, tetapi ini adalah batasan yang sangat
  • Anda harus menyiapkan objek input $scope.datadengan = {}menjadikannya objek (pada dasarnya apa yang $scope.clearData()dilakukan di atas), atau = []untuk array, atau tidak akan berfungsi (kami sudah harus mengasumsikan sesuatu tentang data apa yang akan datang). Saya mencoba melakukan langkah persiapan ini getData, tetapi tidak berhasil.

Namun demikian, ia menyediakan pola yang menghilangkan pelat boiler "janji dibuka", dan mungkin berguna dalam kasus ketika Anda ingin menggunakan data tertentu yang diperoleh dari $ http di lebih dari satu tempat sambil menjaganya tetap KERING.


1

Sejauh menyangkut caching respons dalam layanan, berikut adalah versi lain yang tampaknya lebih jelas daripada yang saya lihat sejauh ini:

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

    })();

});

layanan ini akan mengembalikan data yang di-cache atau $http.get;

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

0

Silakan coba Kode di bawah ini

Anda dapat membagi pengontrol (PageCtrl) dan layanan (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;
             });
         }
    }
}());

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.