Отладка асинхронного JavaScript с помощью Chrome DevTools

Вступление

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

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

К счастью в настоящее время вы можете найти в Chrome Canary DevTools полный стек асинхронных обратных вызовов JavaScript!

Chrome Canary DevTools

Краткий тизер с обзором стека асинхронных вызовов. (В ближайшее время мы детальнее разобьем этот демонстрационный тизер).

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

Просмотреть полную трассировку стека для обработчиков событий, setInterval, setTimeout, XMLHttpRequest, promises, requestAnimationFrame, MutationObservers и другие.

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

Давайте подключим эту функцию и рассмотрим некоторые сценарии.

Подключение асинхронной отладки в Chrome Canary

Можете попробовать эту новую функцию включения асинхронной отладки в Chrome Canary (сборка 35 или выше). Перейдите в меню «Источники» Chrome Canary DevTools.

Рядом с панелью «Стек вызова» справа расположена опция «Async». Переключите чекер в положение включить или отключить. (Хотя в данном случае он включен, вам, возможно, время от времени будет необходимо, чтобы он был отключен):

Стек вызова

Захват отложенных событий таймера и XHR-ответы

Вы, возможно, раньше встречались с такой ситуацией в Gmail:

XHR-ответы

Если возникает проблема с отправкой запроса (или на сервере возникают проблемы, или существуют проблемы подключения к сети на стороне клиента), Gmail автоматически будет повторно отсылать сообщение после короткого промежутка ожидания.

Чтобы пояснить, как стеки асинхронных вызовов могут помочь нам проанализировать отложенные события таймера и XHR-ответы, я воссоздал этот процесс с помощью условной модели Gmail.

Полный код JavaScript вы можете найти по приведенной ссылке, но процесс выглядит следующим образом:

условной модели Gmail

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

В панели «Стек вызовов» предыдущих версий DevTools, можно было по пунктам внутри postOnFail() получить некоторую информацию о том, откуда вызывается postOnFail().

Но посмотрите, как все меняется при подключении асинхронных стеков:

До подключения

До подключения

Панель «Стек вызовов» без возможности подключения асинхронных стеков.

Здесь вы можете увидеть, что postOnFail() был инициирован обратным вызовом AJAX, но это и вся информация.

После подключения

После подключения

Панель «Стек вызовов» с включенными асинхронными стеками.

Здесь вы можете увидеть, что XHR был инициирован функцией submitHandler(), которая в свою очередь была инициирована обработчиком кликов, связанным с scripts.js. Отлично!

С включенным стеком асинхронных вызовов вы можете просмотреть весь стек вызовов, и тогда вы легко увидите, был ли запрос инициирован из submitHandler ( ), как в предыдущем случае, или из retrySubmit(), как показано ниже:

Стек вызовов

С помощью панели «Стек вызовов» вы можете также определить, возникла ли задержка setTimeout() из-за того, что запуск события произошел до события интерфейса, например, «клика», или вследствие другого стандартного события асинхронных обратных вызовов.

Временные выражения асинхронно

Когда вы просматриваете весь стек вызовов, временные выражения также будут обновляться, чтобы отображать состояние, на тот конкретный момент времени!

Временные выражения

Определение кода прошлых областей

Кроме простого пересчета времени выражений вы можете выполнять действия с вашим кодом из предыдущих областей прямо в JavaScript-консоли DevTools.

Представьте, что вы Доктор Кто и вам нужна помощь, чтобы сравнить время на часах на тот момент, когда вы попали в Тардис, и «сейчас».

С помощью консоли DevTools, вы можете легко определять, хранить и выполнять вычисления с разными значениями на разных этапах выполнения:

DevTools

Используйте консоль JavaScript в сочетании с асинхронным стеком вызовов для отладки кода. Демонстрацию описанной процедуры вы можете найти здесь.

Производя все действия через DevTools вы экономите время — в противном случае вам пришлось бы возвращаться к исходному коду, вносить изменения и обновлять страницу в браузере.

Ожидайте вскоре: Разматывание цепочек резолюций promise

Если вы думаете, что в предыдущей модели процесса Gmail было трудно разобраться без функции поддержки асинхронного стека вызова, то можете себе представить, насколько сложнее было бы распутывать более сложные асинхронные процессы, такие как, например, promise-цепочки.

Давайте обратимся к последнему примеру, который Джейк Арчибальд приводит в своем пособии по JavaScript Promises:

JavaScript Promises

Структурная схема JavaScript Promises.

Вот структурная схема просмотра стека вызовов в примере Джейка async-best-example.html.

До

без асинхронных функций

Обратите внимание, что при отладке promise в панели «Стека вызовов» довольно мало информации.

После

с асинхронными функциями

Панель «Стека вызовов» с асинхронными функциями.

Ого! Столько promise. Много обратных вызовов.

Поддержка promise для стеков вызовов будет запущена уже в ближайшее время, как реализация promise, которая подключается из версии в Blink в окончательную версию V8.

Если вы желаете следовать духу времени и уже сейчас хотите посмотреть, как работают стеки асинхронных вызовов для promise, вы можете сделать это в Chrome 33 или Chrome 34.

Перейдите в раздел chrome://flags/#enable-devtools-experiments и включите «Экспериментальные инструменты для разработчиков».

После перезапуска Canary, зайдите в настройки «Инструментов для разработчиков», и там вы найдете опцию включение поддержки трассировки асинхронных стеков.

Разбор веб-анимации

Давайте покопаемся архивах HTML5Rocks. Помните слоган Пола Льюиса Более умная, более насыщенная, более быстрая анимация с requestAnimationFrame?

Откройте демоверсию requestAnimationFrame и установите контрольную точку в начале метода update() (примерно 874 строка) в файле post.html.

С асинхронными стеками вызовов мы можем более предметно рассмотреть requestAnimationFrame. И так же, как в случае с моделью Gmail, мы можем размотать всю цепочку событий до самого начала — инициирующего события ‘scroll’.

До

без асинхронных функций

Панель «Стека вызовов» без асинхронных функций.

После

с асинхронными функциями

Панель «Стека вызовов» с асинхронными функциями.

Отслеживание обновлений DOM, когда используется MutationObserver

MutationObserver позволяет нам наблюдать изменения в DOM. В этом простом примере, после того, как вы нажимаете кнопку, к <div class="rows"></div> добавляется новый DOM узел.

Добавьте контрольную точку в nodeAdded() (строка 31) в demo.html. Теперь с помощью асинхронного стека вызовов вы можете просмотреть стек вызовов от addNode() до инициирующего события клика.

До

nodeAdded()

Панель «Стека вызовов» без асинхронных функций.

После

с асинхронными функциями

Панель «Стека вызовов» с асинхронными функциями.

Советы по отладке JavaScript в асинхронных стеках вызовов

Присваивайте функциям имена

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

Например, возьмем анонимную функцию наподобие этой:

window.addEventListener('load', function(){
  // любое действие
});

И присвоим ей имя наподобие windowLoaded():

window.addEventListener('load', function windowLoaded(){
  // любое действие
});

Когда происходит событие загрузки, оно будет отображаться в трассировке стека DevTools с функцией под определенным именем вместо загадочного «(anonymous function)».

В этом случае его намного проще найти в трассировке стека.

До

событие загрузки до

После

событие загрузки после

Исследуйте дальше

Напомним, все асинхронные обратные вызовы, в которых DevTools будет отображать полный стек вызовов:

  • Таймеры: Прохождение до места, где были инициализированы SetTimeout () или setInterval ( );
  • XHR: Прохождение до места, где был вызван xhr.send();
  • Фреймы анимации: Прохождение до места, где был вызван requestAnimationFrame;
  • Ожидание события: Прохождение до места, где изначально событие было связано с addEventListener();
  • MutationObservers: Прохождение до места, где возникло событие mutation observer.

Полный стек, который вскоре будет доступен в этом экспериментальном JavaScript приложении:

  • Promises: Прохождение до места, где был утвержден promise;
  • Object.observe: Прохождение до места, где изначально был связан вызов обозревателя.

Возможность увидеть полную трассировку стека ваших обратных вызовов JavaScript поможет вам в отладке этих основных направлений.

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

Одним словом, попробуйте Chrome Canary. Если у вас возникли замечания по этой новой функции, напишите нам в группу Chrome DevTools или сообщите об ошибке в системе отслеживания ошибок Chrome DevTools.

Перевод статьи «Debugging Asynchronous JavaScript with Chrome DevTools» был подготовлен дружной командой проекта Сайтостроение от А до Я.