Você pode resolver uma promessa de angularjs antes de devolvê-la?


125

Estou tentando escrever uma função que retorna uma promessa. Mas há momentos em que as informações solicitadas estão disponíveis imediatamente. Quero cumpri-lo com uma promessa para que o consumidor não precise tomar uma decisão.

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 use-o assim:

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

O problema é que o retorno de chamada não é executado para a promessa pré-resolvida. Isso é uma coisa legítima a se fazer? Existe uma maneira melhor de lidar com esta situação?


10
Uma maneira mais simples de escrever o retorno no primeiro caso é return $q.when(Cache[id]). De qualquer forma, isso deve funcionar e chamar o retorno de chamada sempre que você estiver criando novas promessas a cada vez.
12133


1
Crud. Uma hora da minha vida perdida. Eu estava tentando isso em um teste de unidade e a promessa foi cumprida após a conclusão do teste, e eu não estava vendo. Problema no meu teste e não no código.
Craig Celeste

Ligue para $ scope. $ Apply () para garantir que as coisas sejam resolvidas imediatamente durante o teste.
dtabuenc

Acho que httpbackend.flush é responsável por isso, mas $ q pode não ser. Não estou usando o escopo neste teste. Estou testando o serviço diretamente, mas consegui funcionar de qualquer maneira, obrigado.
Craig Celeste

Respostas:


174

Resposta curta: Sim, você pode resolver uma promessa do AngularJS antes de devolvê-la e ela se comportará conforme o esperado.

Do Plunkr de JB Nizet mas refatorado para trabalhar dentro do contexto do que foi originalmente solicitado (ou seja, uma chamada de função para serviço) e realmente no local.

Dentro do serviço ...

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 do controlador ....

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

Eu espero que isso ajude alguém. Não achei as outras respostas muito claras.


2
Não consigo descrever com palavras o quanto estou feliz, você me salvou tanto tempo h.coates!
Ril #

Caso o http GET falhe, a promessa retornada não será rejeitada dessa maneira.
Lex82

5
Portanto, o tl; dr para este post é: Sim, você pode resolver uma promessa antes de devolvê-la e ela entrará em curto-circuito conforme o pretendido.
ray

1
Essa resposta também se aplica ao Q de Kris Kowal, no qual as promessas da Angular se baseiam.
Keith

Adicionei um exemplo de tratamento de erros à sua resposta, espero que esteja tudo bem.
Simon East

98

Como simplesmente retornar uma promessa pré-resolvida no Angular 1.x

Promessa resolvida:

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

Promessa rejeitada:

return $q.reject( someValue );

1
Não há necessidade de que a fábrica, essas funções auxiliares já estão disponíveis:{resolved: $q.when, rejected: $q.reject}
Bergi

Hey Bergi, obrigado por sua valiosa contribuição. Eu editei minha resposta de acordo.
precisa saber é o seguinte

2
Eu acho que essa resposta deve ser selecionada.
Morteza Tourani

@mortezaT Se fosse selecionado, não me renderia um distintivo de ouro. ;)
Andrey Mikhaylov - lolmaus # 10/16

6

Aqui está como eu normalmente faço se realmente quero armazenar dados em cache na matriz ou 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


0

Você esqueceu de inicializar o 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];
    }
}

Desculpe. Isso é verdade. Eu estava tentando simplificar o código para maior clareza na pergunta. Mesmo assim, se cumprir a promessa pré-resolvida, não parece estar chamando o retorno de chamada.
Craig Celeste

2
Eu não acho que se você resolver uma promessa com uma promessa, a promessa interior será achatada. Isso preencheria as Cachepromessas com, em vez dos objetos pretendidos, e o tipo de retorno para os casos em que um objeto estiver no cache e quando não estiver, não será o mesmo. Isso é mais correto, eu acho:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musically_ut

0

Eu gosto de usar uma fábrica para obter os dados dos meus recursos, algo assim.

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

Em seguida, exponha meu modelo no serviço como este aqui

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

        };

    }]);

Em seguida, meus controladores podem incluí-lo e expô-lo ou fazer o que julga correto em seu contexto simplesmente fazendo referência ao Serviço injetado.

Parece funcionar bem. Mas eu sou meio novo em angular. * tratamento de erros principalmente deixado de fora para maior clareza


Seu getStuffmétodo é usando o antipattern diferido
Bergi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.