Статьи

Укрощение асинхронных задач в JavaScript с помощью Zone.js

Недавно я узнал о новом проекте команды Angular под названием Zone . Этот проект является одним из тех редких драгоценных камней, который содержит всего несколько строк кода, но настолько новаторский, что буквально требуется время, чтобы обдумать его. Самый простой способ начать работу с Zone — это посмотреть отличную беседу ее создателя Брайана Форда. Он демонстрирует несколько классных сценариев, которые есть в репозитории проекта.

В двух словах, Zone предоставляет то, что вы можете считать контекстом «выполнения потока» для JavaScript. Он в основном берет текущий «контекст», в котором вы находитесь, и перехватывает все асинхронные события, чтобы они «отображались» в один и тот же контекст. Это позволяет вам делать некоторые довольно интересные вещи, такие как просмотр трасс стека через исходное соединение (т. Е. Вы больше не «отключаетесь» в момент возникновения события) или добавлять инструменты. Вы можете использовать Zone для изменения поведения событий (например, реализации собственной версии setTimeout) и для отслеживания задач.

Зона работает, просто запустив что-то в контексте Зоны. Вы можете иметь несколько зон с собственным контекстом. Чтобы украсть пример из репозитория Zone, подумайте:

zone.run(function () {
  zone.inTheZone = true;

  setTimeout(function () {
    console.log('in the zone: ' + !!zone.inTheZone);
  }, 0);
});

console.log('in the zone: ' + !!zone.inTheZone);

Обратите внимание, что вы можете устанавливать свойства и вызывать асинхронную функцию, и при этом иметь доступ к свойствам в контексте зоны, но после выпадения вы больше не находитесь в зоне .

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

Я начал с идеи простого HTML-фрагмента, который просто содержит кнопку и область для хранения некоторых данных:

<div>
    <button id="myBtn">Populate</button>
    <span id="myData">Nothing</span>
</div>

Затем я пошел в старую школу с некоторыми функциями JavaScript. Я прикрепляю обработчик событий к кнопке, и когда вы нажимаете на нее, она обновляет данные и устанавливает таймер. Таймер срабатывает через 2 секунды и снова обновляет данные. Это выглядит так:

var main = function () {
     var btn = document.getElementById("myBtn"),
         data = document.getElementById("myData");
     btn.addEventListener("click", function () {
         data.innerHTML = "Initializing...";
         setTimeout(function () {
             data.innerHTML = "Done.";
         }, 2000);
     });
};

На этом этапе вы можете вызвать основной метод и увидеть работу приложения. Предположим, вы хотели определить, сколько времени потребуется, чтобы соединить вещи (может быть, вы только что изобрели классный новый фреймворк для привязки данных, например). Как вы отслеживаете асинхронные события и записываете их в правильном порядке? Не стоит беспокоиться. Не беспокойтесь о написании этой библиотеки. Просто потяните в Зону.

Приятной особенностью Zone является то, что вы можете создать шаблон для перехвата действий в зоне. Вы можете хранить определенные переменные или функции в зоне, но, что более важно, вы можете реализовывать функции, которые вызываются при входе и выходе из зоны. Это происходит всякий раз, когда функция выполняется в контексте зоны, включая асинхронные функции. Заимствуя пример производительности в репо, я создал шаблон для зоны профилирования, который выбирает лучший счетчик, доступный для браузера:

var time = 0,
     // use the high-res timer if available
    timer = performance ?
             performance.now.bind(performance) :
             Date.now.bind(Date);

Затем я возвращаю шаблон зоны. Обратите внимание, что я создал собственное свойство с именем «marker», которое я использую, чтобы пометить, где я нахожусь. В коде, который я показал ранее, я буду отмечать зону, вставляя zone.marker = «main» и zone.marker = «click» и zone.marker = «timeout» в начале каждой функции. Это даст мне некоторую полезную информацию позже.

Вот шаблон зоны, который я возвращаю:

return {
     marker: "?",
     onZoneEnter: function () {
         this.originalStart = this.originalStart || timer();
         this.start = timer();
         console.log("Entered task");
     },
     onZoneLeave: function () {
         var diff = timer() - this.start,
             totalDiff = timer() - this.originalStart;
         console.log("Exited task " + zone.marker + " after " + diff);
         time += diff;
         console.log("Total active time: " + time);
         console.log("Total elapsed time: " + totalDiff);
     },
     reset: function () {
         time = 0;
     }
};

 Теперь все, что мне нужно сделать, это обернуть вещи в зоне. Поскольку контекст обрабатывает все внутри в, нет никаких модификаций основного метода. Zone будет обрабатывать все события и направлять их в мой контекст для меня. Единственное, что мне нужно сделать, кроме получения самого кода зоны, это вызвать main следующим образом:

zone.fork(profilingZone).run(function () {
     zone.reset();
     main();
});

Это создает копию на основе моего шаблона и запускает основную функцию в контексте. Теперь я просто запускаю пример, подожду несколько секунд и нажму кнопку. Вот что отображается в моей консоли. Обратите внимание, я не изменял свой основной метод, кроме как для установки маркеров. Вся аппаратура была автоматически добавлена ​​зоной. Обратите внимание, что общее истекшее время велико, потому что я буквально запустил это и ждал, когда я написал статью, прежде чем нажать кнопку. Это нормально, потому что зона была в состоянии отслеживать! Разница между двумя последними общими истекшими временами составляет приблизительно 2 секунды или интервал, который я установил на тайм-ауте.

Entered task
Exited task main after 2.0000000076834112
Total active time: 2.0000000076834112
Total elapsed time: 2.0000000076834112
Entered task
Exited task click after 2.0000000076834112
Total active time: 4.0000000153668225
Total elapsed time: 830135.0000000093
Entered task
Exited task timeout after 0
Total active time: 4.0000000153668225
Total elapsed time: 832138.0000000063

Насколько это мощно? Одной из распространенных жалоб на Angular является цикл дайджеста, который используется для сканирования моделей и слушателей и поиска изменений в модели. Это необходимо для того, чтобы реагировать на внешние изменения, и нужно вызывать (применять) специальный метод, когда вы изменяете модель вне этого цикла. Иногда цикл должен запускаться несколько раз, чтобы были обнаружены определенные побочные эффекты (т.е. изменение здесь вызывает изменение там, что означает, что другое место должно быть обновлено). Зона позволит команде Angular запустить весь цикл внутри контекста зоны, и команда считает, что это устранит необходимость в дайджесте или будет применяться полностью!

Чтобы убедиться в этом, посмотрите эту скрипку . Я еще не знаю CDN для Zone, поэтому я просто включил весь источник в скрипку.