¿Puedes resolver una promesa angularjs antes de devolverla?

125

Estoy tratando de escribir una función que devuelva una promesa. Pero hay momentos en que la información solicitada está disponible de inmediato. Quiero envolverlo en una promesa para que el consumidor no tenga que tomar una decisión.

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

Y úsalo así:

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

El problema es que la devolución de llamada no se ejecuta para la promesa resuelta previamente. ¿Es esto algo legítimo? ¿Hay una mejor manera de manejar esta situación?

Craig Celeste
fuente
10
Una forma más simple de escribir la declaración en el primer caso es return $q.when(Cache[id]). De todos modos, esto debería funcionar y llamar a la devolución de llamada cada vez, ya que está creando nuevas promesas cada vez.
musically_ut
1
Crud. Una hora de mi vida perdida. Estaba probando esto en una prueba unitaria y la promesa se cumple una vez que se completa la prueba, y no la estaba viendo. Problema con mi prueba y no con el código.
Craig Celeste
Asegúrese de llamar a $ scope. $ Apply () para asegurarse de que las cosas se resuelvan de inmediato durante la prueba.
dtabuenc
Creo que httpbackend.flush explica esto, pero $ q podría no serlo. No estoy usando el alcance en esta prueba. Estoy probando el servicio directamente, pero lo hice funcionar de todos modos, gracias.
Craig Celeste

Respuestas:

174

Respuesta corta: Sí, puede resolver una promesa de AngularJS antes de devolverla, y se comportará como es de esperar.

Desde Plunkr de JB Nizet pero refactorizado para trabajar dentro del contexto de lo que se solicitó originalmente (es decir, una llamada de función al servicio) y realmente en el sitio.

Dentro del servicio ...

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;

}

Dentro del controlador ...

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

Espero que esto ayude a alguien. No encontré las otras respuestas muy claras.

h.coates
fuente
2
No puedo describir con palabras lo feliz que estoy, me has ahorrado tanto tiempo.
rilar
En caso de que el http GET falle, la promesa devuelta no se rechaza de esta manera.
lex82
55
Entonces, el tl; dr para esta publicación es: Sí, puede resolver una promesa antes de devolverla, y se cortocircuitará según lo previsto.
Ray
1
Esta respuesta también se aplica a la Q de Kris Kowal en la que se basan las promesas de Angular.
Keith
Agregué un ejemplo de manejo de errores a su respuesta, espero que esté bien.
Simon East
98

Cómo simplemente devolver una promesa resuelta previamente en Angular 1.x

Promesa resuelta:

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

Promesa rechazada:

return $q.reject( someValue );
Andrey Mikhaylov - lolmaus
fuente
1
No es necesario para esa fábrica, esas funciones auxiliares ya están disponibles:{resolved: $q.when, rejected: $q.reject}
Bergi
Hola Bergi, gracias por tu valiosa contribución. He editado mi respuesta en consecuencia.
Andrey Mikhaylov - lolmaus
2
Creo que esta respuesta debería seleccionarse.
Morteza Tourani
@mortezaT Si fuera seleccionado, no me daría una insignia dorada. ;)
Andrey Mikhaylov - lolmaus
6

Así es como lo hago normalmente si realmente quiero almacenar en caché los datos en una matriz u objeto

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
fuente
0

Olvidó inicializar el elemento Caché

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
fuente
Lo siento. Es verdad. Intenté simplificar el código para aclarar la pregunta. Aun así, si entra en la promesa pre-resuelta, no parece estar llamando la devolución de llamada.
Craig Celeste
2
No creo que si resuelves una promesa con una promesa, la promesa interna se aplana. Esto llenaría las Cachepromesas en lugar de los objetos deseados y el tipo de retorno para los casos en que un objeto está en la caché y cuando no lo está, no será el mismo. Esto es más correcto, creo:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musically_ut
0

Me gusta usar una fábrica para obtener datos de mi recurso.

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

Luego exponga mi modelo en el servicio como este aquí

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

        };

    }]);

Luego, mis controladores pueden incluirlo y exponerlo o hacer lo que parece correcto en su contexto simplemente haciendo referencia al Servicio inyectado.

Parece funcionar bien. Pero soy un poco nuevo en angular. * el manejo de errores se omitió principalmente para mayor claridad

Frank Swanson
fuente
Su getStuffmétodo está utilizando el antipatrón diferido
Bergi