Вступление
Возможность асинхронной работы с помощью callback-функций(далее просто возвращаемых функций) — отличительная особенность JavaScript. Использование асинхронных возвращаемых функций позволяет вам писать событийно-ориентированный код, но так же добавляет кучу проблем, ведь код перестает выполняться в линейной последовательности.
К счастью, теперь в Chrome Canary DevTools вы можете отслеживать весь стек вызовов асинхронных функций в JavaScript!
Небольшая презентация стека асинхронных вызовов.
Как только вы включите поддержку стека вызовов асинхронных функций в DevTools, вы сможете получить развернутую информацию о состоянии вашего веб приложения в любые моменты времени. Прогуливаться по полному стеку выполнения ваших обработчиков событий, функций setInterval, setTimeout, XMLHttpRequest, promises, requestAnimationFrame, MutationObservers и так далее.
Помимо того, что вы можете просматривать стек вызовов, вы так же можете анализировать значения любых переменных в любой момент выполнения функций. Это похоже на машину времени для наблюдения за выражениями.
Давайте включим поддержку этого функционала и пробежимся по нескольким сценариям.
Включение поддержки асинхронной отладки кода в Chrome Canary
Попробуйте новый функционал, включив его поддержку в Chrome Canary(build 35 и выше). Откройте страницу Sources в Chrome Canary DevTools.
Чекбокс «Async» находится на панели, следующей за панелью стека вызовов(Call Stack) с правой стороны. В зависимости от положения этого чекбокса, возможность отладки асинхронных функций будет включена или выключена(хотя, однажды его включив, скорее всего вы никогда не захотите его выключить).
Перехват отложенных событий и XHR ответов
Вы, возможно, уже видели подобное раньше в Gmail:
Если произойдет ошибка в отсылке запроса(возникнут проблемы на стороне сервера или проблемы с соединением с клиентской стороны), Gmail попробует переслать письмо черзе небольшой промежуток времнеи.
Чтобы продемонстрировать, как стек асинхронных вызовов сможет нам помочь проанализировать отложенные события и XHR ответы, я воосоздала описанную выше ситуацию с примером заглушки Gmail. Весь JavaScript код примера может быть найден по ссылке выше. Алгоритм его работы выглядит следующим образом:
На схеме выше, методы, подсвеченные синим являются главными местами для применения новых возможностей DevTools, так как эти методы выполняются асинхронно.
Если смотреть только в панель стека вызовов(Call Stack) в предыдущих версиях DevTools, точка останова(breakpoint) в функции postOnFail() даст вам лишь немного информации о том, где функция postOnFail() была вызвана. А теперь давайте взглянем на разницу со случаем, когда мы работаем со включенной возможностью отладки асинхронных вызовов:
До | После |
Панель стека вызовов без поддержки асинхронных запросов Здесь вы можете увидеть, что вызов функции postOnFail() произошел из возвращаемой фукнции AJAX, но ничего более. |
Панель стека вызовов с поддержкой асинхронных запросов Здесь вы можете увидеть, что XHR был вызван из submitHandler(), который был вызван обработчиком события клика, объявленным в scripts.js. Круто! |
При использовании стека асинхронных вызовов, мы можем легко просматривать все цепочки вызовов. Например, мы можем определить, был ли вызов метода submitHandler() иницирован так же, как описано выше, или же через retrySubmit() как описано ниже:
Используя панель стека вызовов, вы так же можете сказать, привело ли к точке останова срабатывание события пользовательского интерфейса(например, клик), задержка setTimeout() или работа асинхронной возвращаемой функции.
Наблюдайте за выражениями асинхронно
Когда вы просматриваете ваш стек вызовов, ваши выражения, добавленные к отслеживанию, будут обновляться, чтобы отразить состояние, которое они принимали в указанный момент времени!
Выполняйте код из предыдущих состояний
В дополнение к простому наблюдению за выражениями, вы так же можете взаимодействовать со своим кодом из предыдущих состояний прямо в DevTools JavaScript консоли.
Представьте, будто вы Доктор Кто(Dr. Who) и вам нужна небольшая помощь в сравнении часов, которые у вас были до этого момента в Тардисе(Tardis) и «сейчас». Из консоли DevTools вы можете легко исполнять, сохранять и производить вычисления над значениями из разных точек выполнения.
Использование JavaScript консоли для отладки асинхронного стека вызовов вашего кода.
Пример выше может быть найден здесь
Оставаясь в DevTools для манипуляций с вашими выражениями, вы экономите время, которое бы у вас заняло возвращение предыдущих состояний кода, внесение изменений, и обновление страницы браузера.
На подходе: Отладка цепочек обещаний(promise)
Если в прошлом примере с Gmail вам было тяжело распутывать стек асинхронных вызовов без новой возможности DevTools, то вы, вероятно, можете себе представить, насколько труднее это будет проделать с более сложными асинхронными очередями, например, с цепочкой обещаний? Давайте еще раз пробежимся по последнему примеру из обучения по обещаниям в JavaScript от Джейка Арчибальда.
Блок-схема JavaScript обещаний
Вот небольшая анимация работы со стеком вызовов в лучшем асинхронном примере от Джейка:
До:
Панель стека вызовов без поддержки асинхронной отладки
Обратите внимание, как мало информации предоставляет панель стека вызовов, когда мы отлаживаем обещания.
После:
Панель стека вызовов с поддержкой асинхронной отладки
Вау, какие обещания! Огромное количество возвращаемых функций.
Примечание:
Поддержка обещаний для стека вызова будет готова очень скоро, как только реализация обещаний перерастет из версии в Blink в финальную версию в V8.
В духе возвращения назад во времени, если вы захотите сегодня посмотреть на асинхронные стеки вызовов для обещаний, вы можете воспользоваться версиями Chrome 33 или Chrome 34. Зайдите в chrome://flags/#enable-devtools-experiments и подключите поддержку эксперементальных DevTools. После перезагрузки Canary, зайдите в настройки DevTools, там появится возможность включить поддержку асинхронных стеков вызовов.
Погружаемся глубже в веб анимации
Давайте пойдем глубже в архивы HTML5Rocks. Помните статью Паула Левиса «Более компактные, производительные и быстрые анимации с помощью requestAnimationFrame»?
Откройте requestAnimationFrame демо и добавьте точку останова в начале метода update()(это где-то 874 строка) файла post.html. Со асинхронным стеком вызовов мы сможем «окунуться глубже» в requestAnimationFrame. И, так же, как и в примере с заглушкой Gmail, у нас появляется возможность пройтись назад к начальному событию(это событие scroll).
До | После |
Отслеживаем изменения DOM с помощью MutationObserver
MutationObserver позволяет нам наблюдать за событиями в DOM. Рассмотрим простой пример: когда вы кликаете на кнопку, новый элемент DOM добавляется к блоку .
Добавим новую точку останова в функции nodeAdded() (строка 31) в файле demo.html. С помощью асинхронного стека вызовов, вы можете пройтись по истории вызовов назад через метод addNode() к начальному событию клика.
До | После |
Примечания к отладке JavaScript кода с помощью асинхронного стека вызовов
Называйте свои функции
Если вы привыкли добавлять все ваши возвращаемые функции как анонимные функции, то, возможно, сейчас стоит пересмотреть своё решение и начать давать функциям имена, которые помогут вам сделать отладку с использованием асинхронного стека вызовов еще проще.
Например, возьмите анонимную функцию:
window.addEventListener('load', function(){
// do something
});
И дайте ей название. Скажем, windowLoaded():
window.addEventListener('load', function windowLoaded(){
// do something
});
Когда возникнет событие загрузки документа, это будет отражено при выводе стека с помощью DevTools, где вместо мистического названия "(anonymous function)" будет красоваться полное имя функции. Это заметно упростит читабельность истории вызовов, и в большинстве случаев позволит с первого взгляда понять, что там происходит.
До | После |
Что дальше
Резюмируя все вышесказанное, все эти асинхронные возвращаемые функции будут отображать полный стек вызовов:
- Таймеры: Возвращают назад до момента инициализации setTimeout() или setInterval()
- XHRы: Возвращают назад до момента, где был вызван xhr.send()
- Анимации: Возвращают назад до момента вызова requestAnimationFrame
- Обработчики событий: Возвращают назад до момента установки обработчика с помощью addEventListener()
- Наблюдатели изменений: Возвращают назад до момента, где было вызвано события изменения
Так же полный стек изменений можно будет посмотреть в ближайшее время для следующих JavaScript API:
- Обещания: Возвращают назад до момента, где обещания было выполнено
- Object.observe: Возвращает назад до момента прикрепления возвращаемой функции
Возможность просмотра всего стека ваших возвращаемых функций должно быть ставит дыбом ваши волосы. Это возможность в DevTools будет особенно полезна, когда несколько асинхронных событий случается в зависимости друг от друга, или когда неперехваченное сообщение об ошибке возникает в одной из ваших асинхронных возвращаемых функций.
Дайте им шанс в Chrome Canary. Если у вас есть что нам сказать по этому поводу, напишите нам в группу Chrome DevTools или присылайте нам баги в наш Chrome DevTools багтрекер.