Puoi risolvere una promessa angularjs prima di restituirla?


125

Sto cercando di scrivere una funzione che restituisca una promessa. Ma ci sono momenti in cui le informazioni richieste sono immediatamente disponibili. Voglio racchiuderlo in una promessa in modo che il consumatore non debba prendere una decisione.

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

E usalo in questo modo:

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

Il problema è che il callback non viene eseguito per la promessa pre-risolta. È una cosa legittima da fare? Esiste un modo migliore per gestire questa situazione?


10
Un modo più semplice per scrivere il ritorno nel primo caso è return $q.when(Cache[id]). Ad ogni modo, questo dovrebbe funzionare e richiamare ogni volta il callback poiché si stanno creando nuove promesse ogni volta.
musically_ut


1
Crud. Un'ora della mia vita persa. Lo stavo provando in un unit test e la promessa è stata mantenuta dopo che il test è stato completato, e non lo vedevo. Problema con il mio test e non con il codice.
Craig Celeste,

Assicurati di chiamare $ scope. $ Apply () per assicurarti che le cose si risolvano immediatamente durante il test.
Dtabuenc,

Penso che httpbackend.flush conti per questo, ma $ q potrebbe non esserlo. Non sto usando l'ambito in questo test. Sto testando direttamente il servizio, ma l'ho fatto funzionare comunque, grazie.
Craig Celeste,

Risposte:


174

Risposta breve: Sì, puoi risolvere una promessa di AngularJS prima di restituirla e si comporterà come ti aspetteresti.

Da Plunkr di JB Nizet ma rifattorizzato per funzionare nel contesto di ciò che è stato originariamente richiesto (cioè una funzione chiamata al servizio) e in realtà sul sito.

All'interno del servizio ...

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;

}

All'interno del controller ....

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

Spero che aiuti qualcuno. Non ho trovato le altre risposte molto chiare.


2
Non posso descrivere a parole quanto sono felice, mi hai risparmiato così tanto tempo h.coates!
rilar

Nel caso in cui il HTTP GET fallisca, la promessa restituita non viene rifiutata in questo modo.
lex82,

5
Quindi il tl; dr per questo post è: Sì, puoi risolvere una promessa prima di restituirla, e andrà in corto circuito come previsto.
ray

1
Questa risposta vale anche per la Q di Kris Kowal su cui si basano le promesse di Angular.
Keith,

Ho aggiunto un esempio di gestione degli errori alla tua risposta, spero che vada bene.
Simon East,

98

Come semplicemente restituire una promessa pre-risolta in Angular 1.x

Promessa risolta:

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

Promessa rifiutata:

return $q.reject( someValue );

1
Non è necessario per quella fabbrica, quelle funzioni di supporto sono già disponibili:{resolved: $q.when, rejected: $q.reject}
Bergi

Ehi Bergi, grazie per la tua preziosa donazione. Ho modificato la mia risposta di conseguenza.
Andrey Mikhaylov - lolmaus,

2
Penso che questa risposta dovrebbe essere selezionata.
Morteza Tourani,

@mortezaT Se fosse selezionato, non mi darebbe un distintivo d'oro. ;)
Andrey Mikhaylov - lolmaus,

6

Ecco come lo faccio in genere se voglio effettivamente memorizzare nella cache i dati nell'array o nell'oggetto

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


0

Hai dimenticato di inizializzare l'elemento Cache

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

Scusate. È vero. Stavo cercando di semplificare il codice per chiarezza nella domanda. Anche così, se rientra nella promessa pre-risolta, non sembra chiamare il callback.
Craig Celeste,

2
Non penso che se risolvi una promessa con una promessa, la promessa interiore è appiattita. Ciò popolerebbe le Cachepromesse invece degli oggetti previsti e il tipo restituito per i casi in cui un oggetto si trova nella cache e quando non lo è non sarà lo stesso. Questo è più corretto, penso:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musically_ut

0

Mi piace usare una fabbrica per ottenere qualcosa di simile ai miei dati dalla mia risorsa.

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

Quindi esporre il mio modello nel servizio come questo qui

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

        };

    }]);

Quindi i miei controller possono includerlo ed esporlo o fare ciò che si abbassa è giusto nel suo contesto semplicemente facendo riferimento al servizio iniettato.

Sembra funzionare bene. Ma sono un po 'nuovo di angolare. * gestione degli errori per lo più esclusa per chiarezza


Il tuo getStuffmetodo sta usando l' antipasto differito
Bergi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.