Пост подготовлен на основе статьи “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 занимается дайджест.
Вместо вывода могу сказать, что создание нового метода выглядит как микро-оптимизация, которая дает возможность фреймворку группировать ваши асинхронные выражения и выполнять их в рамках одного дайджеста.