Пост подготовлен на основе статьи “Scope.$applyAsync() vs. Scope.$evalAsync() in AngularJS 1.3” от Ben Nadel.
Из Angular1.2 мы все знаем метод скоупа $evalAsync
, который позволяет вызывать код асинхронно (относительно цикла дайджеста) не прибегая к использованию сервиса $timeout.
В Angular1.3 был добавлен еще один метод – $applyAsync
. После прочтения документации может оказаться, что по прежнему не ясно отличие этих 2х методов. С этим мы и попробуем разобраться в посте.
- Оба метода обновляют глобальную очередь выражений ожидающих выполнения (но у каждого метода своя очередь:
asyncQueue
иapplyAsyncQueue
). - Оба метода выполняют выражение асинхронно, но
$evalAsync()
позволяет переопределить локальные зависимости(locals) - Оба метода выполняют выражение в
try/catch
блоке и отлавливают исключительные ситуации с помощью$exceptionHandler
сервиса.
С первого взгляда методы идентичны. Различия становятся очевидными только, когда вы смотрите последовательность выполнения в цикле дайджеста: очередь $applyAsync
– будет выполнена перед тем как Ангуляр начнет “грязную проверку”, то есть выполниться только один раз, а очередь $evalAsync
будет выполнена перед каждым проходом цикла. Это означает что любое выражение добавленное в $evalAsync
очередь во время цикла дайджеста будет выполнено на следующем шаге этого же цикла.
- Оба метода выполняют свои очереди и после чего по таймеру запускают
$digest
на$rootScope
.
И даже сейчас я не представляю зачем мы должны использовать новый метод $applyAsync
. Вероятно для того, чтобы позволить отрисовывать DOM до выполнения выражения.
Чтобы получить больше ответов я погрузился в исходный код и обнаружил, что $applyAsync()
метод используется самим фремворком – внутри $http
сервиса, похоже $httpProvider
теперь позволяет группировать ajax
запросы идущие в интервале 10мс (вместо того, чтобы вызывать дайджест после каждого запроса)
Основываясь на исходном коде и выкинув все проверки я сделал что-то типа прототипа для обоих методов:
[javascript]
function $evalAsync(expr, locals) {
$browser.defer($rootScope.$digest);
asyncQueue.push({scope: this, expression: expr, locals: locals});
}
function $applyAsync(expr) {
var scope = this;
applyAsyncQueue.push(function(){
scope.$eval(expr);
});
$browser.defer(function() {
while (applyAsyncQueue.length) {
applyAsyncQueue.shift()();
}
applyAsyncId = null;
});
$browser.defer($rootScope.$digest);
}
[/javascript]
Откуда видно, что $applyAsync
сам контроллирует свою очередь, а вот очередью $evalAsync
занимается дайджест.
Вместо вывода могу сказать, что создание нового метода выглядит как микро-оптимизация, которая дает возможность фреймворку группировать ваши асинхронные выражения и выполнять их в рамках одного дайджеста.