Вызов AngularJS $http в службе, возврат разрешенных данных, а не обещаний

Я хочу знать, можно ли сделать вызов службы, который использует $http, чтобы он возвращал данные напрямую, не возвращая обещание? Я безуспешно пытался использовать $q и откладывать.

Вот что я имею в виду:

У меня есть услуга:

angular.module('myModule').factor('myService', ['$http', '$q',
    function($http, $q) {

        // Public API
        return {
            myServiceCall: function() {
                return $http.get('/server/call');
            }
        };
    }
]);

И вот как я бы это назвал:

// My controller:
myService.myServiceCall().then(function(data) {
  $scope.data = data;
});

Я хочу избежать этого и вместо этого хочу иметь:

$scope.data = myService.myServiceCall();

Я хочу, чтобы это было полностью разрешено в этой строке, возможно ли это?

Я пробовал некоторые комбинации методов $q, defer и 'then', но не могу понять это правильно, поскольку метод возвращается немедленно.

Изменить:

Если вам интересно, почему, основная причина в том, что я хотел упростить код контроллера, это легко сделать с помощью ngResource, поскольку эти вызовы автоматически разрешаются в шаблонах, поэтому я хотел избежать необходимости делать весь '.then' каждый раз.

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

Я думаю, что на данный момент, как некоторые из вас указали, я буду использовать $resource для достаточно близкого решения.


person iQ.    schedule 01.12.2014    source источник
comment
Это асинхронная операция, поэтому ответ не совсем. Вы можете использовать $resource из ngResource, который делает какое-то странное внутреннее разрешение обещаний, например, $scope.data = $resource('/server/call').get()   -  person Phil    schedule 02.12.2014
comment
Что касается того, почему, мне нужно использовать «тогда», чтобы получить данные, если бы это был ng-ресурс, это было бы здорово, поскольку шаблоны автоматически разрешают их. Однако для $http мне нужно каждый раз разрешать его вручную на контроллере, я думаю, я хотел иметь чистоту ng-ресурса.   -  person iQ.    schedule 02.12.2014


Ответы (3)


Я могу придумать два способа сделать это. Первое однозначно лучше второго.

Первый способ: использование свойства resolve маршрутов

Вы можете использовать свойство resolve объекта конфигурации маршрута, чтобы указать, что обещание должно быть разрешено, а разрешенное значение должно использоваться для значения одной из ваших зависимостей контроллера, чтобы, как только ваш контроллер запустится, значение было немедленно доступно.

Например, предположим, что у вас есть следующий сервис:

app.service('things', ['$http', '$q', function ($http, $q) {
    var deferred = $q.defer();

    $http({
        method: 'GET',
        url: '/things',
        cache: true
    }).success(function (data) {
        deferred.resolve(data);
    }).error(function (msg) {
        deferred.reject(msg);
    });

    return deferred.promise;
}]);

Затем вы можете определить свой маршрут примерно так:

$routeProvider.when('/things', {
    controller: ['$scope', 'thingsData', function ($scope, thingsData) {
        $scope.allThings = thingsData;
    }],
    resolve: {
        thingsData: ['things', function (things) {
            return things;
        }]
    }
});

Ключ в объекте resolve соответствует имени второй зависимости для контроллера, поэтому даже если он возвращает обещание, это обещание будет разрешено до запуска контроллера.

Второй способ: сделайте запрос задолго до того, как вам понадобятся данные

Второй способ, который совсем не идеален и, вероятно, в какой-то момент укусит вас за задницу, заключается в том, чтобы сделать запрос данных на ранней стадии, например, когда ваше приложение инициализируется, а затем фактически получить данные позже. , скажем, на странице, для перехода на которую требуется 3 клика с главной страницы. Скрестим пальцы, если звезды сошлись, ваш http-запрос будет возвращен к тому времени, и у вас будут данные для использования.

angular.module('myModule').factory('myService', ['$http', '$q', function($http, $q) {
    var data = [];

    function fetch() {
        $http.get('/server/call').then(function (d) { data = d; });
    }

    // Public API
    return {
        fetchData: fetch,
        myServiceCall: function () {
            return data;
        }
    };
}]);

Затем в функции запуска вашего модуля сделайте запрос:

angular.module('myModule').run(['myService', function (myService) {
    myService.fetchData();
});

Затем в контроллере для страницы, которая гарантированно запустится как минимум через 3 секунды после запуска приложения, вы можете сделать так, как вы предложили:

 angular.module('myModule').controller('deepPageCtrl', ['$scope', 'myService', function ($scope, myService) {
      $scope.data = myService.myServiceCall();
      if (angular.isArray($scope.data) && $scope.data.length === 0) {
           panic("data isn't loaded yet");
      }
  }]);

Бонус, столь же плохой третий способ: синхронный запрос AJAX

Сделайте, как предлагает ricick, и используйте синхронный запрос AJAX, возможный с помощью метода jQuery .ajax().

person GregL    schedule 01.12.2014
comment
Интересно, я никогда не знал о втором методе. - person iQ.; 02.12.2014

Это невозможно, так как HTTP-вызовы по своей сути асинхронны. То есть нужно дождаться ответа сервера.

Служба $resource обходит это, возвращая прокси-объект, который заполняется возвращенными данными при ответе сервера, но он все еще не полностью разрешен в одной строке.

Изменить:

На самом деле можно выполнять синхронные HTTP-запросы с помощью метода jquery ajax, установив для параметра «async» значение false.

Вы можете создать службу, которая обертывает это, однако ваше приложение будет «заблокировано», пока запрос ожидает ответа сервера, поэтому в большинстве случаев это будет плохой практикой. Существуют и другие ограничения на этот тип запроса.

См. документы для более подробной информации:

http://api.jquery.com/jQuery.ajax/

person ricick    schedule 01.12.2014

Давным-давно (в угловатых землях, где-то в прошлом году) так оно и было.

Но теперь это не так, я думаю, это связано с различиями в поведении обещаний в представлении и контроллере, подробнее здесь об устаревании: Angularjs обещает не привязываться к шаблону в 1.2

person Boris Charpentier    schedule 01.12.2014