Können Sie ein eckiges Versprechen lösen, bevor Sie es zurückgeben?

125

Ich versuche eine Funktion zu schreiben, die ein Versprechen zurückgibt. Es gibt jedoch Situationen, in denen die angeforderten Informationen sofort verfügbar sind. Ich möchte es in ein Versprechen einwickeln, damit der Verbraucher keine Entscheidung treffen muss.

function getSomething(id) {
    if (Cache[id]) {
        var deferred = $q.defer();
        deferred.resolve(Cache[id]); // <-- Can I do this?
        return deferred.promise;
    } else {
        return $http.get('/someUrl', {id:id});
    }
}

Und benutze es so:

somethingService.getSomething(5).then(function(thing) {
    alert(thing);
});

Das Problem ist, dass der Rückruf für das vorgelöste Versprechen nicht ausgeführt wird. Ist das eine legitime Sache? Gibt es einen besseren Weg, um mit dieser Situation umzugehen?

Craig Celeste
quelle
10
Eine einfachere Möglichkeit, die Rückgabe im ersten Fall zu schreiben, ist return $q.when(Cache[id]). Auf jeden Fall sollte dies funktionieren und den Rückruf jedes Mal aufrufen, da Sie jedes Mal neue Versprechen erstellen.
musikalisch_ut
1
Roh. Eine Stunde meines Lebens verloren. Ich habe dies in einem Unit-Test versucht und das Versprechen wird erfüllt, nachdem der Test abgeschlossen ist, und ich habe es nicht gesehen. Problem mit meinem Test und nicht mit dem Code.
Craig Celeste
Stellen Sie sicher, dass Sie $ scope. $ Apply () aufrufen, um sicherzustellen, dass die Probleme beim Testen sofort behoben werden.
dtabuenc
Ich denke, httpbackend.flush erklärt dies, aber $ q möglicherweise nicht. Ich verwende in diesem Test keinen Bereich. Ich teste den Service direkt, aber ich habe ihn trotzdem zum Laufen gebracht, danke.
Craig Celeste

Antworten:

174

Kurze Antwort: Ja, Sie können ein AngularJS-Versprechen lösen, bevor Sie es zurücksenden, und es verhält sich wie erwartet.

Von JB Nizets Plunkr, aber überarbeitet , um im Kontext dessen zu arbeiten, was ursprünglich gefragt wurde (dh ein Funktionsaufruf zum Service) und tatsächlich vor Ort.

Innerhalb des Dienstes ...

function getSomething(id) {
    // There will always be a promise so always declare it.
    var deferred = $q.defer();
    if (Cache[id]) {
        // Resolve the deferred $q object before returning the promise
        deferred.resolve(Cache[id]); 
        return deferred.promise;
    } 
    // else- not in cache 
    $http.get('/someUrl', {id:id}).success(function(data){
        // Store your data or what ever.... 
        // Then resolve
        deferred.resolve(data);               
    }).error(function(data, status, headers, config) {
        deferred.reject("Error: request returned status " + status); 
    });
    return deferred.promise;

}

Im Controller ....

somethingService.getSomething(5).then(    
    function(thing) {     // On success
        alert(thing);
    },
    function(message) {   // On failure
        alert(message);
    }
);

Ich hoffe es hilft jemandem. Ich fand die anderen Antworten nicht sehr klar.

h.coates
quelle
2
Ich kann nicht mit Worten beschreiben, wie glücklich ich bin, du hast mir so viel Zeit gespart h.coates!
Rilar
Falls das http GET fehlschlägt, wird das zurückgegebene Versprechen auf diese Weise nicht abgelehnt.
Lex82
5
Die Antwort für diesen Beitrag lautet also: Ja, Sie können ein Versprechen lösen, bevor Sie es zurücksenden, und es wird wie beabsichtigt kurzgeschlossen.
Ray
1
Diese Antwort gilt auch für Kris Kowals Q, auf dem Angulars Versprechen basieren.
Keith
Ich habe Ihrer Antwort ein Beispiel für die Fehlerbehandlung hinzugefügt. Ich hoffe, das ist in Ordnung.
Simon East
98

So geben Sie einfach ein vorgelöstes Versprechen in Angular 1.x zurück

Gelöstes Versprechen:

return $q.when( someValue );    // angular 1.2+
return $q.resolve( someValue ); // angular 1.4+, alias to `when` to match ES6

Abgelehntes Versprechen:

return $q.reject( someValue );
Andrey Mikhaylov - lolmaus
quelle
1
{resolved: $q.when, rejected: $q.reject}
Diese
Hey Bergi, danke für deinen wertvollen Beitrag. Ich habe meine Antwort entsprechend bearbeitet.
Andrey Mikhaylov - lolmaus
2
Ich denke, diese Antwort sollte ausgewählt werden.
Morteza Tourani
@mortezaT Wenn es ausgewählt wäre, würde es mir kein goldenes Abzeichen geben. ;)
Andrey Mikhaylov - lolmaus
6

So mache ich das normalerweise, wenn ich Daten in einem Array oder Objekt zwischenspeichern möchte

app.factory('DataService', function($q, $http) {
  var cache = {};
  var service= {       
    getData: function(id, callback) {
      var deffered = $q.defer();
      if (cache[id]) {         
        deffered.resolve(cache[id])
      } else {            
        $http.get('data.json').then(function(res) {
          cache[id] = res.data;              
          deffered.resolve(cache[id])
        })
      }
      return deffered.promise.then(callback)
    }
  }

  return service

})

DEMO

charlietfl
quelle
0

Sie haben vergessen, das Cache-Element zu initialisieren

function getSomething(id) {
    if (Cache[id]) {
        var deferred = $q.defer();
        deferred.resolve(Cache[id]); // <-- Can I do this?
        return deferred.promise;
    } else {
        Cache[id] = $http.get('/someUrl', {id:id});
        return Cache[id];
    }
}
zs2020
quelle
Es tut uns leid. Das ist richtig. Ich habe versucht, den Code aus Gründen der Klarheit in der Frage zu vereinfachen. Trotzdem scheint es nicht den Rückruf zu rufen, wenn es in das vorgelöste Versprechen geht.
Craig Celeste
2
Ich denke nicht, wenn Sie ein Versprechen mit einem Versprechen lösen, wird das innere Versprechen abgeflacht. Dies würde das Cachemit Versprechungen anstelle der beabsichtigten Objekte füllen und der Rückgabetyp für die Fälle, in denen sich ein Objekt im Cache befindet und wenn es nicht ist, wird nicht dasselbe sein. Das ist korrekter, denke ich:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musikalisch_ut
0

Ich benutze gerne eine Fabrik, um die Daten von meiner Ressource zu erhalten.

.factory("SweetFactory", [ "$http", "$q", "$resource", function( $http, $q, $resource ) {
    return $resource("/sweet/app", {}, {
        "put": {
            method: "PUT",
            isArray: false
        },"get": {
            method: "GET",
            isArray: false
        }
    });
}]);

Dann stellen Sie mein Modell hier wie folgt in den Dienst

 .service("SweetService",  [ "$q", "$filter",  "$log", "SweetFactory",
    function ($q, $filter, $log, SweetFactory) {

        var service = this;

        //Object that may be exposed by a controller if desired update using get and put methods provided
        service.stuff={
            //all kinds of stuff
        };

        service.listOfStuff = [
            {value:"", text:"Please Select"},
            {value:"stuff", text:"stuff"}];

        service.getStuff = function () {

            var deferred = $q.defer();

          var promise = SweetFactory.get().$promise.then(
                function (response) {
                    if (response.response.result.code !== "COOL_BABY") {
                        deferred.reject(response);
                    } else {
                        deferred.resolve(response);
                        console.log("stuff is got", service.alerts);
                        return deferred.promise;
                    }

                }
            ).catch(
                function (error) {
                    deferred.reject(error);
                    console.log("failed to get stuff");
                }
            );

            promise.then(function(response){
                //...do some stuff to sett your stuff maybe fancy it up
                service.stuff.formattedStuff = $filter('stuffFormatter')(service.stuff);

            });


            return service.stuff;
        };


        service.putStuff = function () {
            console.log("putting stuff eh", service.stuff);

            //maybe do stuff to your stuff

            AlertsFactory.put(service.stuff).$promise.then(function (response) {
                console.log("yep yep", response.response.code);
                service.getStuff();
            }).catch(function (errorData) {
                alert("Failed to update stuff" + errorData.response.code);
            });

        };

    }]);

Dann können meine Controller es einschließen und verfügbar machen oder das tun, was es für richtig hält, indem sie einfach auf den injizierten Service verweisen

Scheint in Ordnung zu funktionieren. Aber ich bin ein bisschen neu im Winkel. * Fehlerbehandlung aus Gründen der Übersichtlichkeit meistens weggelassen

Frank Swanson
quelle
Ihre getStuffMethode verwendet das verzögerte
Antimuster