Как функция $resource `get` работает синхронно в AngularJS?

Я смотрел this Учебник AngularJS, описывающий, как подключиться к Twitter с ресурсом Angular. (Видеоурок) Вот ресурс, который настроен в примере контроллера:

$scope.twitter = $resource('http://twitter.com/:action',
    {action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
    {get: {method: 'JSONP'}});

В учебнике показано, что есть несколько способов вернуть данные из ресурса с помощью вызова get. Первый способ - передать обратный вызов функции get. Обратный вызов будет вызываться с результатом после возврата запроса ajax:

$scope.twitter.get(function(result) {
    console.log('This was the result:', result);
});

Я понимаю этот метод. Это имеет для меня смысл. Ресурс представляет собой место в Интернете, где вы можете получить данные, а get просто делает вызов ajax на url, возвращает json и вызывает функцию обратного вызова с помощью json. Параметр result - это json.

Это имеет смысл для меня, потому что кажется очевидным, что это асинхронный вызов. То есть под капотом запускается вызов ajax, а код, следующий за вызовом, не блокируется, он продолжает выполняться. Затем в какой-то неопределенной точке позже, когда xhr будет успешным, вызывается функция обратного вызова.

Затем в учебнике показан другой метод, который выглядит намного проще, но я не понимаю, как он работает:

$scope.twitterResult = $scope.twitter.get();

Я предполагаю, что xhr под get должен быть асинхронным, но в этой строке мы присваиваем возвращаемое значение вызова get переменной, как если бы она возвращалась синхронно.

Неужели я ошибаюсь, потому что не понимаю этого? Как это возможно? Я думаю, что это действительно опрятно, что это работает, я просто не понимаю.

Я понимаю, что get может возвращать что-то, в то время как xhr под ним отключается и обрабатывается асинхронно, но если вы будете следовать примеру кода самостоятельно, вы увидите, что $scope.twitterResult получает фактическое содержимое твиттера перед выполнением любых последующих строк, Например, если вы пишете console.log($scope.twitterResult) сразу после этой строки, вы увидите результаты твиттера, зарегистрированные в консоли, а не временное значение, которое будет заменено позже.

Что еще более важно, поскольку это возможно, как я могу написать службу Angular, которая использует эту же функциональность? Помимо аякс-запросов, существуют другие типы хранилищ данных, требующих асинхронных вызовов, которые могут использоваться в JavaScript, которые я бы хотел написать синхронно в этом стиле. Например, IndexedDB. Если бы я мог окутать голову, как это делают Angular встроенные ресурсы, я бы дал ему шанс.

Ответ 1

Ресурс

$не является синхронным, хотя этот синтаксис может предполагать, что он:

$scope.twitterResult = $scope.twitter.get();

Что здесь происходит, так это то, что вызов AngularJS после вызова twitter.get(), немедленно вернется с результатом, являющимся пустым массивом. Затем, когда асинхронный вызов завершен и реальные данные поступают с сервера, массив будет обновляться с данными. AngularJS просто сохранит ссылку на возвращаемый массив и заполнит его, когда будут доступны данные.

Вот фрагмент реализации $resource, где происходит "волшебство": https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L372

Это описано в $resource documentation:

Важно понимать, что вызов метода объекта $resource немедленно возвращает пустую ссылку (объект или массив в зависимости от isArray). Как только данные возвращаются с сервера, существующая ссылка заполняется фактическими данными. Это полезный трюк, поскольку обычно ресурс присваивается модели, которая затем отображается в виде. Наличие пустого объекта не приводит к рендерингу, как только данные поступают с сервера, тогда объект заполняется данными, и представление автоматически повторно отображает себя, показывая новые данные. Это означает, что в большинстве случаев никогда не нужно писать функцию обратного вызова для методов действий.

Ответ 2

$q тоже может сделать этот трюк. Вы можете преобразовать обычный объект в "задержанное значение", используя что-то вроде этого:

var delayedValue = function($scope, deferred, value) {
    setTimeout(function() {
        $scope.$apply(function () {
            deferred.resolve(value);
        });
    }, 1000);
    return deferred.promise;
};

а затем использовать его в контроллере, чтобы получить аналогичный эффект для того, что $scope.twitter.get() делает в примере OP

angular.module('someApp', [])
.controller('someController', ['$scope', '$q', function($scope, $q) {
  var deferred = $q.defer();
  $scope.numbers = delayedValue($scope, deferred, ['some', 'numbers']);
}]);