Можете ли вы выполнить обещание angularjs, прежде чем вернуть его?

125

Я пытаюсь написать функцию, возвращающую обещание. Но бывают случаи, когда запрошенная информация доступна сразу. Я хочу обернуть это обещанием, чтобы потребителю не нужно было принимать решение.

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

И используйте это так:

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

Проблема в том, что обратный вызов не выполняется для предварительно разрешенного обещания. Это законный поступок? Есть ли лучший способ справиться с этой ситуацией?

Крейг Селеста
источник
10
Более простой способ написать возврат в первом случае - это return $q.when(Cache[id]). Во всяком случае, это должно работать и вызывать обратный вызов каждый раз, поскольку вы каждый раз создаете новые обещания.
musically_ut
3
Работает: plnkr.co/edit/OGO8T2M1fE3Mrgj2oozj?p=preview
JB Nizet
1
Crud. Потерян час моей жизни. Я пробовал это в модульном тесте, и обещание выполнено после завершения теста, и я этого не видел. Проблема с моим тестом, а не с кодом.
Craig Celeste
Убедитесь, что вы вызываете $ scope. $ Apply (), чтобы убедиться, что все разрешается сразу во время тестирования.
dtabuenc
Я думаю, что httpbackend.flush учитывает это, а $ q - нет. Я не использую прицел в этом тесте. Я тестирую сервис напрямую, но все равно он работает, спасибо.
Craig Celeste

Ответы:

174

Краткий ответ: Да, вы можете выполнить обещание AngularJS, прежде чем вернуть его, и оно будет вести себя так, как вы ожидаете.

Из Plunkr от JB Nizet, но после рефакторинга для работы в контексте того, что было изначально задано (то есть вызов функции для службы) и фактически на месте.

Внутри сервиса ...

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;

}

Внутри контроллера ....

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

Надеюсь, это кому-то поможет. Я не нашел других ответов очень ясными.

h.coates
источник
2
Я не могу описать словами, как я счастлива, ты сэкономил мне столько времени, h.coates!
rilar 02
В случае сбоя http GET возвращенное обещание таким образом не отклоняется.
lex82
5
Итак, tl; dr для этого сообщения: Да, вы можете разрешить обещание, прежде чем возвращать его, и оно произойдет короткое замыкание, как задумано.
Рэй
1
Этот ответ также относится к Q Криса Ковала, на котором основаны обещания Angular.
Кейт
Я добавил к вашему ответу пример обработки ошибок, надеюсь, что все в порядке.
Саймон Ист
98

Как просто вернуть предварительно обработанное обещание в Angular 1.x

Решенное обещание:

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

Отклоненное обещание:

return $q.reject( someValue );
Андрей Михайлов - лолмаус
источник
1
Нет необходимости в этой фабрике, эти вспомогательные функции уже доступны:{resolved: $q.when, rejected: $q.reject}
Берги,
Привет, Берги, спасибо за ваш ценный вклад. Я соответствующим образом отредактировал свой ответ.
Андрей Михайлов - lolmaus
2
Думаю, стоит выбрать этот ответ.
Мортеза Турани
@mortezaT Если бы он был выбран, я бы не получил золотой значок. ;)
Андрей Михайлов - lolmaus
6

Вот как я обычно это делаю, если хочу кэшировать данные в массиве или объекте

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
источник
0

Вы забыли инициализировать элемент 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];
    }
}
zs2020
источник
Сожалею. Это правда. Я пытался упростить код для ясности вопроса. Даже в этом случае, если он входит в предварительно разрешенное обещание, похоже, он не вызывает обратный вызов.
Craig Celeste
2
Я не думаю, что если вы разрешаете обещание с помощью обещания, внутреннее обещание сглаживается. Это заполнит Cacheобещания вместо предполагаемых объектов, а тип возвращаемого значения для случаев, когда объект находится в кэше, а когда его нет, не будет таким же. Я думаю, это более правильно:$http.get('/someUrl', {id: id}).then(function (response) { Cache[id] = response.data; return Cache[id]; });
musically_ut
0

Мне нравится использовать фабрику для получения данных с моего ресурса, например.

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

Затем выставьте мою модель в сервисе вот так

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

        };

    }]);

Затем мои контроллеры могут включить его и раскрыть или сделать то, что он считает правильным в его контексте, просто ссылаясь на внедренный Service.

Вроде работает нормально. Но я новичок в angular. * обработка ошибок в основном не учитывается для ясности

Фрэнк Свонсон
источник
Ваш getStuffметод использует отложенный антипаттерн
Берги