Verarbeitung der $ http-Antwort im Dienst


233

Ich habe kürzlich eine detaillierte Beschreibung des Problems veröffentlicht, mit dem ich hier bei SO konfrontiert bin . Da ich keine tatsächliche $httpAnfrage senden konnte , habe ich Timeout verwendet, um asynchrones Verhalten zu simulieren. Die Datenbindung von meinem Modell zur Ansicht funktioniert mithilfe von @Gloopy ordnungsgemäß

Wenn ich jetzt $httpanstelle von $timeout(lokal getestet) verwende, kann ich feststellen, dass die asynchrone Anforderung erfolgreich war und datain meinem Dienst mit einer JSON-Antwort gefüllt ist. Meine Ansicht wird jedoch nicht aktualisiert.

Plunkr hier aktualisiert

Antworten:


419

Hier ist ein Plunk, der macht, was Sie wollen: http://plnkr.co/edit/TTlbSv?p=preview

Die Idee ist, dass Sie direkt mit Versprechungen und deren "Dann" -Funktionen arbeiten, um die asynchron zurückgegebenen Antworten zu bearbeiten und darauf zuzugreifen.

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

Hier ist eine etwas kompliziertere Version, die die Anforderung zwischenspeichert, sodass Sie sie nur beim ersten Mal erstellen ( 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
Gibt es eine Möglichkeit, die Erfolgs- und Fehlermethoden in der Steuerung nach dem Abfangen des Dienstes weiterhin aufzurufen then?
andyczerwonka

2
@PeteBD Wenn ich meine myService.async()Dienste mehrmals von verschiedenen Controllern aus aufrufen möchte , wie würden Sie den Dienst so organisieren, dass nur $http.get()die erste Anforderung ausgeführt wird und alle nachfolgenden Anforderungen nur ein lokales Objektarray zurückgeben, das beim ersten Aufruf an festgelegt wird myService.async(). Mit anderen Worten, ich möchte mehrere unnötige Anforderungen an den JSON-Dienst vermeiden, wenn ich wirklich nur eine stellen muss.
GFoley83

5
@ GFoley83 - los geht's: plnkr.co/edit/2yH1F4IMZlMS8QsV9rHv?p=preview . Wenn Sie sich die Konsole ansehen, werden Sie feststellen, dass die Anforderung nur einmal erfolgt.
Pete BD

3
@PeteBD Ich denke, Sie können auch $scope.data = myService.async()direkt in der Steuerung verwenden.
Julian

2
@ Blowsie- Ich habe die Plunks aktualisiert. Hier ist das Original (aktualisiert auf 1.2RC3): plnkr.co/edit/3Nwxxk?p=preview Hier ist einer, der den Dienst verwendet: plnkr.co/edit/a993Mn?p=preview
Pete BD

82

Lass es einfach sein. Es ist so einfach wie

  1. Rückkehr promisein Ihren Service (keine Notwendigkeit, thenim Service zu verwenden)
  2. Verwenden Sie thenin Ihrem Controller

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

In Ihrem Link ist es app.factoryund in Ihrem Code ist es app.service. Es wird app.factoryin diesem Fall angenommen.
Re Captcha

1
app.service funktioniert auch. Auch - das sieht für mich nach der elegantesten Lösung aus. Vermisse ich etwas
user1679130

1
Scheint, als hätte jedes Mal, wenn ich ein Winkelproblem habe, @allenhwkim die Antwort! (3. Mal in dieser Woche
Yarin

Ich möchte nur wissen, wie man Erfolg und Irrtum hier mit status_code
Anuj

58

Da es asynchron $scopeist, werden die Daten abgerufen, bevor der Ajax-Aufruf abgeschlossen ist.

Sie können es $qin Ihrem Dienst verwenden, um promisees zu erstellen und an den Controller zurückzugeben, und der Controller erhält das Ergebnis innerhalb des then()Aufrufs gegen promise.

In Ihrem Dienst,

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

Dann in Ihrem Controller:

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

2
+1 Ich mag dieses am besten, da es mehr OO als die anderen ist. Gibt es jedoch einen Grund, warum Sie dies nicht tun this.async = function() {und this.getData = function() {return data}? Ich hoffe du bekommst was ich meine
Fahrrad

@bicycle Ich wollte es genauso, aber es wird nicht funktionieren, weil das Versprechen vollständig gelöst werden muss. Wenn Sie dies nicht tun und versuchen, wie gewohnt darauf zuzugreifen, wird beim Zugriff auf die internen Daten ein Referenzfehler angezeigt. Hoffe es macht Sinn?
user6123723

Wenn ich das richtig verstehe, muss es deffered = $q.defer()in myService.async hinzugefügt werden, wenn ich myService.async () zwei oder mehr Mal aufrufen möchte
demas

1
Dieses Beispiel ist ein klassisches verzögertes Anti-Muster . Es ist nicht erforderlich, ein Versprechen zu erstellen, $q.deferda der $httpService bereits ein Versprechen zurückgibt. Das zurückgegebene Versprechen bleibt hängen, wenn $httpein Fehler zurückgegeben wird. Außerdem sind die Methoden .successund .errorveraltet und wurden aus AngularJS 1.6 entfernt .
Georgeawg

23

tosh shimayama hat eine Lösung, aber Sie können viel vereinfachen, wenn Sie die Tatsache nutzen, dass $ http Versprechen zurückgibt und dass Versprechen einen Wert zurückgeben können:

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

});

Eine kleine Demonstration in Coffeescript: http://plunker.no.de/edit/ksnErx?live=preview

Ihr Plunker wurde mit meiner Methode aktualisiert: http://plnkr.co/edit/mwSZGK?p=preview


Ich werde es weiter versuchen. Aber ich möchte das Ergebnis im Service erfassen, anstatt zurückzukehren. Die diesbezügliche Frage finden Sie hier stackoverflow.com/questions/12504747/… . Ich möchte die von $ http zurückgegebenen Daten im Controller auf unterschiedliche Weise verarbeiten. Danke nochmal für deine Hilfe.
BSR

Sie können Versprechen in Diensten verwenden. Wenn Sie $ watch nicht mögen, können Sie ´promise.then (function (data) {service.data = data;}, onErrorCallback); `
Guillaume86

Ich habe einen von Ihnen gegabelten Plunker hinzugefügt
Guillaume86

1
Alternativ können Sie $ scope. $ emit from the service und $ scope. $ on auf der Strg-Taste verwenden, um dem Controller mitzuteilen, dass die Daten zurückgegeben wurden, aber ich sehe keinen wirklichen Vorteil
Guillaume86

7

Ein viel besserer Weg, denke ich, wäre so etwas:

Bedienung:

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

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

        ...

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

        ...

        return deferred.promise;
    }

    return{
        getAllFruits:getAllFruits
    }

});

Und in der Steuerung können Sie einfach verwenden:

$scope.fruits = FruitsManager.getAllFruits();

Angular setzt das aufgelöste automatisch awesomeFruitsin das $scope.fruits.


4
deferred.resolve ()? Seien Sie bitte genauer und wo ist der $ http-Aufruf? Warum geben Sie ein Objekt in einem .service zurück?

6

Ich hatte das gleiche Problem, aber als ich im Internet surfte, verstand ich, dass $ http standardmäßig ein Versprechen zurückgibt, dann könnte ich es mit "dann" verwenden, nachdem ich die "Daten" zurückgegeben habe. Schauen Sie sich den Code an:

 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

Wenn Sie die Benutzeroberfläche an Ihr Array binden, sollten Sie sicherstellen, dass Sie dasselbe Array direkt aktualisieren, indem Sie die Länge auf 0 setzen und die Daten in das Array übertragen.

Stattdessen (wodurch eine andere Array-Referenz festgelegt wird, von datader Ihre Benutzeroberfläche nichts weiß):

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

Versuche dies:

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

Hier ist eine Geige , die den Unterschied zwischen dem Festlegen eines neuen Arrays und dem Leeren und Hinzufügen zu einem vorhandenen Array zeigt. Ich konnte dein plnkr nicht zum Laufen bringen, aber hoffentlich funktioniert das für dich!


das hat nicht funktioniert. Im Konsolenprotokoll konnte ich sehen, dass d beim erfolgreichen Rückruf ordnungsgemäß aktualisiert wurde, jedoch keine Daten. Möglicherweise ist die Funktion bereits ausgeführt.
BSR

Diese Methode sollte auf jeden Fall funktionieren, vielleicht hat sie etwas damit zu tun, dass der Datentyp d kein Array ist (in asp.net müssten Sie beispielsweise auf dd für das Array zugreifen). In diesem plnkr finden Sie ein Beispiel für das Verschieben eines Strings in das Array bei einem Fehler: plnkr.co/edit/7FuwlN?p=preview
Gloopy

1
angular.copy(d, data)wird auch funktionieren. Wenn ein Ziel an die copy () -Methode übergeben wird, werden zuerst die Elemente des Ziels gelöscht und dann die neuen Elemente aus der Quelle kopiert.
Mark Rajcok

4

Im Zusammenhang damit habe ich ein ähnliches Problem durchlaufen, jedoch nicht mit get oder post von Angular, sondern mit einer Erweiterung von einem Drittanbieter (in meinem Fall Chrome Extension).
Das Problem, mit dem ich konfrontiert war, ist, dass die Chrome-Erweiterung nicht zurückgegeben then()wird. Daher konnte ich dies nicht wie in der obigen Lösung tun, aber das Ergebnis ist immer noch asynchron.
Meine Lösung besteht also darin, einen Dienst zu erstellen und zu einem Rückruf überzugehen

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

Dann in meinem Controller

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

Hoffe, dies kann anderen helfen, das gleiche Problem zu bekommen.


4

Ich habe http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/ gelesen. [AngularJS ermöglicht es uns, unsere Controller-Logik zu optimieren, indem wir ein Versprechen direkt auf den Bereich legen, anstatt das Gelöste manuell zu übergeben Wert in einem erfolgreichen Rückruf.]

so einfach und praktisch :)

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

Ich hoffe das hilft


funktioniert nicht Der Rückgabewert von defrred.promiseist keine Funktion.
Jürgen Paul

@PineappleUndertheSea warum muss es eine Funktion sein? Es ist ein Versprechensobjekt.
Chev

@PineappleUndertheSea Wollten Sie verzögert und nicht verzögert verwenden?
Derrick

2
Wie PeteBD wies darauf hin, diese Form $scope.items = Data.getData(); ist in Anglular veraltet
poshest

2

Ich mag die Tatsache wirklich nicht, dass der Verbraucher des Dienstes, der $ http verwendet, aufgrund der "vielversprechenden" Vorgehensweise "wissen" muss, wie die Antwort entpackt werden soll.

Ich möchte nur etwas anrufen und die Daten herausholen, ähnlich wie bei der alten $scope.items = Data.getData();Methode, die jetzt veraltet ist .

Ich habe es eine Weile versucht und keine perfekte Lösung gefunden, aber hier ist mein bester Schuss ( Plunker ). Es kann für jemanden nützlich sein.

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

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

Mängel, die ich bereits erkennen kann, sind

  • Sie müssen das Objekt übergeben, zu dem die Daten hinzugefügt werden sollen. Dies ist in Angular kein intuitives oder allgemeines Muster
  • getDatakann den objParameter nur in Form eines Objekts akzeptieren (obwohl er auch ein Array akzeptieren könnte), was für viele Anwendungen kein Problem darstellt, aber eine schmerzhafte Einschränkung darstellt
  • Sie haben die Eingabeobjekt vorzubereiten $scope.datamit = {}ihm ein Objekt ( im Wesentlichen , was zu machen $scope.clearData()oben der Fall ist), oder = []für ein Array, oder es wird nicht funktionieren (wir haben schon etwas zu übernehmen , welche Daten kommt). Ich habe versucht, diesen Vorbereitungsschritt IN durchzuführen getData, aber kein Glück.

Nichtsdestotrotz bietet es ein Muster, das das Controller-Boilerplate "Versprechen auspacken" entfernt und in Fällen nützlich sein kann, in denen Sie bestimmte Daten aus $ http an mehr als einer Stelle verwenden möchten, während Sie es trocken halten.


1

Was das Zwischenspeichern der Antwort im Dienst betrifft, ist hier eine andere Version, die direkter zu sein scheint als das, was ich bisher gesehen habe:

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

    })();

});

Dieser Dienst gibt entweder die zwischengespeicherten Daten zurück oder $http.get;

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

0

Bitte versuchen Sie den folgenden Code

Sie können den Controller (PageCtrl) und den Service (dataService) aufteilen.

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.