Как функция $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']);
}]);