Как остановить выполнение js на странице в браузере
Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».
Для этого существуют два метода:
- setTimeout позволяет вызвать функцию один раз через определённый интервал времени.
- setInterval позволяет вызывать функцию регулярно, повторяя вызов через определённый интервал времени.
Эти методы не являются частью спецификации JavaScript. Но большинство сред выполнения JS-кода имеют внутренний планировщик и предоставляют доступ к этим методам. В частности, они поддерживаются во всех браузерах и Node.js.
setTimeout
func|code Функция или строка кода для выполнения. Обычно это функция. По историческим причинам можно передать и строку кода, но это не рекомендуется. delay Задержка перед запуском в миллисекундах (1000 мс = 1 с). Значение по умолчанию – 0. arg1 , arg2 … Аргументы, передаваемые в функцию (не поддерживается в IE9-)
Например, данный код вызывает sayHi() спустя одну секунду:
Если первый аргумент является строкой, то JavaScript создаст из неё функцию.
Это также будет работать:
Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так:
Начинающие разработчики иногда ошибаются, добавляя скобки () после функции:
Это не работает, потому что setTimeout ожидает ссылку на функцию. Здесь sayHi() запускает выполнение функции, и результат выполнения отправляется в setTimeout . В нашем случае результатом выполнения sayHi() является undefined (так как функция ничего не возвращает), поэтому ничего не планируется.
Отмена через clearTimeout
Вызов setTimeout возвращает «идентификатор таймера» timerId , который можно использовать для отмены дальнейшего выполнения.
Синтаксис для отмены:
В коде ниже планируем вызов функции и затем отменяем его (просто передумали). В результате ничего не происходит:
Как мы видим из вывода alert , в браузере идентификатором таймера является число. В других средах это может быть что-то ещё. Например, Node.js возвращает объект таймера с дополнительными методами.
Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным.
Для браузеров таймеры описаны в разделе таймеров стандарта HTML5.
setInterval
Метод setInterval имеет такой же синтаксис как setTimeout :
Все аргументы имеют такое же значение. Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.
Чтобы остановить дальнейшее выполнение функции, необходимо вызвать clearInterval(timerId) .
В большинстве браузеров, включая Chrome и Firefox, внутренний счётчик продолжает тикать во время показа alert/confirm/prompt .
Вложенный setTimeout
Есть два способа запускать что-то регулярно.
Один из них setInterval . Другим является вложенный setTimeout . Например:
Метод setTimeout выше планирует следующий вызов прямо после окончания текущего (*) .
Вложенный setTimeout – более гибкий метод, чем setInterval . С его помощью последующий вызов может быть задан по-разному в зависимости от результатов предыдущего.
Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд… Вот псевдокод:
А если функции, которые мы планируем, ресурсоёмкие и требуют времени, то мы можем измерить время, затраченное на выполнение, и спланировать следующий вызов раньше или позже.
Вложенный setTimeout позволяет задать задержку между выполнениями более точно, чем setInterval .
Сравним два фрагмента кода. Первый использует setInterval :
Второй использует вложенный setTimeout :
Для setInterval внутренний планировщик будет выполнять func(i) каждые 100 мс:
Реальная задержка между вызовами func с помощью setInterval меньше, чем указано в коде!
Это нормально, потому что время, затраченное на выполнение func , использует часть заданного интервала времени.
Вполне возможно, что выполнение func будет дольше, чем мы ожидали, и займёт более 100 мс.
В данном случае движок ждёт окончания выполнения func и затем проверяет планировщик и, если время истекло, немедленно запускает его снова.
В крайнем случае, если функция всегда выполняется дольше, чем задержка delay , то вызовы будут выполняться без задержек вообще.
Ниже представлено изображение, показывающее процесс работы рекурсивного setTimeout :
Вложенный setTimeout гарантирует фиксированную задержку (здесь 100 мс).
Это потому, что новый вызов планируется в конце предыдущего.
Когда функция передаётся в setInterval/setTimeout , на неё создаётся внутренняя ссылка и сохраняется в планировщике. Это предотвращает попадание функции в сборщик мусора, даже если на неё нет других ссылок.
Для setInterval функция остаётся в памяти до тех пор, пока не будет вызван clearInterval .
Есть и побочный эффект. Функция ссылается на внешнее лексическое окружение, поэтому пока она существует, внешние переменные существуют тоже. Они могут занимать больше памяти, чем сама функция. Поэтому, если регулярный вызов функции больше не нужен, то лучше отменить его, даже если функция очень маленькая.
setTimeout с нулевой задержкой
Особый вариант использования: setTimeout(func, 0) или просто setTimeout(func) .
Это планирует вызов func настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода.
Так вызов функции будет запланирован сразу после выполнения текущего кода.
Например, этот код выводит «Привет» и затем сразу «Мир»:
Первая строка помещает вызов в «календарь» через 0 мс. Но планировщик проверит «календарь» только после того, как текущий код завершится. Поэтому "Привет" выводится первым, а "Мир" – после него.
Есть и более продвинутые случаи использования нулевой задержки в браузерах, которые мы рассмотрим в главе Событийный цикл: микрозадачи и макрозадачи.
В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В стандарте HTML5 говорится: «после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.».
Продемонстрируем в примере ниже, что это означает. Вызов setTimeout повторно вызывает себя через 0 мс. Каждый вызов запоминает реальное время от предыдущего вызова в массиве times . Какова реальная задержка? Посмотрим:
Первый таймер запускается сразу (как и указано в спецификации), а затем задержка вступает в игру, и мы видим 9, 15, 20, 24. .
Аналогичное происходит при использовании setInterval вместо setTimeout : setInterval(f) запускает f несколько раз с нулевой задержкой, а затем с задержкой 4+ мс.
Это ограничение существует давно, многие скрипты полагаются на него, поэтому оно сохраняется по историческим причинам.
Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, setImmediate для Node.js. Так что это ограничение относится только к браузерам.
Итого
- Методы setInterval(func, delay, . args) и setTimeout(func, delay, . args) позволяют выполнять func регулярно или только один раз после задержки delay , заданной в мс.
- Для отмены выполнения необходимо вызвать clearInterval/clearTimeout со значением, которое возвращают методы setInterval/setTimeout .
- Вложенный вызов setTimeout является более гибкой альтернативой setInterval . Также он позволяет более точно задать интервал между выполнениями.
- Планирование с нулевой задержкой setTimeout(func,0) или, что то же самое, setTimeout(func) используется для вызовов, которые должны быть исполнены как можно скорее, после завершения исполнения текущего кода.
- Браузер ограничивает 4-мя мс минимальную задержку между пятью и более вложенными вызовами setTimeout , а также для setInterval , начиная с 5-го вызова.
Обратим внимание, что все методы планирования не гарантируют точную задержку.
Например, таймер в браузере может замедляться по многим причинам:
- Перегружен процессор.
- Вкладка браузера в фоновом режиме.
- Работа ноутбука от аккумулятора.
Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС.
Задачи
Вывод каждую секунду
Напишите функцию printNumbers(from, to) , которая выводит число каждую секунду, начиная от from и заканчивая to .
Давайте отвлечёмся от написания кода и поговорим о его отладке.
Отладка – это процесс поиска и исправления ошибок в скрипте. Все современные браузеры и большинство других сред разработки поддерживают инструменты для отладки – специальный графический интерфейс, который сильно упрощает отладку. Он также позволяет по шагам отследить, что именно происходит в нашем коде.
Мы будем использовать браузер Chrome, так как у него достаточно возможностей, в большинстве других браузеров процесс будет схожим.
Панель «Исходный код» («Sources»)
Версия Chrome, установленная у вас, может выглядеть немного иначе, однако принципиальных отличий не будет.
- Работая в Chrome, откройте тестовую страницу.
- Включите инструменты разработчика, нажав F12 (Mac: Cmd + Opt + I ).
- Щёлкните по панели Sources («исходный код»).
При первом запуске получаем следующее:
Кнопка-переключатель откроет вкладку со списком файлов.
Кликните на неё и выберите hello.js в дереве файлов. Вот что появится:
Интерфейс состоит из трёх зон:
- В зоне File Navigator (панель для навигации файлов) показаны файлы HTML, JavaScript, CSS, включая изображения, используемые на странице. Здесь также могут быть файлы различных расширений Chrome.
- Зона Code Editor (редактор кода) показывает исходный код.
- Наконец, зона JavaScript Debugging (панель отладки JavaScript) отведена для отладки, скоро мы к ней вернёмся.
Чтобы скрыть список ресурсов и освободить экранное место для исходного кода, щёлкните по тому же переключателю .
Консоль
При нажатии на клавишу Esc в нижней части экрана вызывается консоль, где можно вводить команды и выполнять их клавишей Enter .
Результат выполнения инструкций сразу же отображается в консоли.
Например, результатом 1+2 будет 3 , а вызов функции hello("debugger") ничего не возвращает, так что результатом будет undefined :
Точки останова (breakpoints)
Давайте разберёмся, как работает код нашей тестовой страницы. В файле hello.js щёлкните на номере строки 4 . Да-да, щёлкайте именно по самой цифре, не по коду.
Ура! Вы поставили точку останова. А теперь щёлкните по цифре 8 на восьмой линии.
Вот что в итоге должно получиться (синим это те места, по которым вы должны щёлкнуть):
Точка останова – это участок кода, где отладчик автоматически приостановит исполнение JavaScript.
Пока исполнение поставлено «на паузу», мы можем просмотреть текущие значения переменных, выполнить команды в консоли, другими словами, выполнить отладку кода.
В правой части графического интерфейса мы видим список точек останова. А когда таких точек выставлено много, да ещё и в разных файлах, этот список поможет эффективно ими управлять:
- Быстро перейдите к точке останова в коде (нажав на неё на правой панели).
- Временно отключите точку останова, сняв с неё галочку.
- Удалите точку останова, щёлкнув правой кнопкой мыши и выбрав Remove (Удалить).
- …и так далее.
Щелчок правой кнопкой мыши по номеру строки позволяет создать условную точку останова. Она сработает только в тот момент, когда выражение, которое вы должны указать при создании такой точки, истинно.
Это удобно, когда нам нужно остановиться только для при определённом значении переменной или для определённых параметров функции.
Команда debugger
Выполнение кода можно также приостановить с помощью команды debugger прямо изнутри самого кода:
Такая команда сработает только если открыты инструменты разработки, иначе браузер ее проигнорирует.
Остановимся и оглядимся
В нашем примере функция hello() вызывается во время загрузки страницы, поэтому для начала отладки (после того, как мы поставили точки останова) проще всего её перезагрузить. Нажмите F5 (Windows, Linux) или Cmd + R (Mac).
Выполнение прервётся на четвёртой строчке (где находится точка останова):
Чтобы понять, что происходит в коде, щёлкните по стрелочкам справа:
Watch – показывает текущие значения для любых выражений.
Вы можете нажать на + и ввести выражение. Отладчик покажет его значение, автоматически пересчитывая его в процессе выполнения.
Call Stack – показывает цепочку вложенных вызовов.
В текущий момент отладчик находится внутри вызова hello() , вызываемого скриптом в index.html (там нет функции, поэтому она называется “анонимной”).
Если вы нажмёте на элемент стека (например, «anonymous»), отладчик перейдёт к соответствующему коду, и нам представляется возможность его проанализировать.
Scope показывает текущие переменные.
Local показывает локальные переменные функций, а их значения подсвечены прямо в исходном коде.
В Global перечисляются глобальные переменные (то есть вне каких-либо функций).
Там также есть ключевое слово this , которое мы ещё не изучали, но скоро изучим.
Пошаговое выполнение скрипта
А теперь давайте пошагаем по нашему скрипту.
Для этого есть кнопки в верхней части правой панели. Давайте рассмотрим их.
– «Resume»: продолжить выполнение, быстрая клавиша F8 .
Возобновляет выполнение кода. Если больше нет точек останова, то выполнение просто продолжается, без контроля отладчиком.
Вот, что мы увидим, кликнув на неё:
Выполнение кода возобновилось, дошло до другой точки останова внутри say() , и отладчик снова приостановил выполнение. Обратите внимание на пункт «Call stack» справа: в списке появился ещё один вызов. Сейчас мы внутри say() .
– «Step»: выполнить следующую команду, быстрая клавиша F9 .
Выполняет следующую инструкцию. Если мы нажмём на неё сейчас, появится alert .
Нажатие на эту кнопку снова и снова приведёт к пошаговому выполнению всех инструкций скрипта одного за другим.
– «Step over»: выполнить следующую команду, но не заходя внутрь функции, быстрая клавиша F10 .
Работает аналогично предыдущей команде «Step», но ведёт себя по-другому, если следующая инструкция является вызовом функции (имеется ввиду: не встроенная, как alert , а объявленная нами функция).
Если сравнить, то команда «Step» переходит во вложенный вызов функцию и приостанавливает выполнение в первой строке, в то время как «Step over» выполняет вызов вложенной функции незаметно для нас, пропуская её внутренний код.
Затем выполнение приостанавливается сразу после вызова функции.
Это хорошо, если нам не интересно видеть, что происходит внутри вызова функции.
– «Step into», быстрая клавиша F11 .
Это похоже на «Step», но ведёт себя по-другому в случае асинхронных вызовов функций. Если вы только начинаете изучать JavaScript, то можете не обращать внимания на разницу, так как у нас ещё нет асинхронных вызовов.
На будущее просто помните, что команда «Step» игнорирует асинхронные действия, такие как setTimeout (вызов функции по расписанию), которые выполняются позже. «Step into» входит в их код, ожидая их, если это необходимо. См. DevTools manual для получения более подробной информации.
– «Step out»: продолжить выполнение до завершения текущей функции, быстрая клавиша Shift + F11 .
Продолжает выполнение и останавливает его в самой последней строке текущей функции. Это удобно, когда мы случайно вошли во вложенный вызов, используя , но это нас не интересует, и мы хотим продолжить его до конца как можно скорее.
– активировать/деактивировать все точки останова(breakpoints).
Эта кнопка не влияет на выполнение кода, она лишь позволяет массово включить/отключить точки останова.
– включить/отключить автоматическую паузу в случае ошибки.
При включении, если открыты инструменты разработчика, ошибка при выполнении скрипта автоматически приостанавливает его. Затем мы можем проанализировать переменные в отладчике, чтобы понять, что пошло не так. Поэтому, если наш скрипт умирает с ошибкой, мы можем открыть отладчик, включить эту опцию и перезагрузить страницу, чтобы увидеть, где он умирает и каков контекст в этот момент.
Щелчок правой кнопкой мыши по строке кода открывает контекстное меню с отличной опцией под названием «Continue to here» («продолжить до этого места»).
Это удобно, когда мы хотим перейти на несколько шагов вперёд к строке, но лень устанавливать точку останова (breakpoint).
Логирование
Чтобы вывести что-то на консоль из нашего кода, существует функция console.log .
Если вы занимаетесь веб-программированием, это значит, что вам не надо рассказывать о том, насколько в вашей работе важна отладка. Нередко для записи данных в логи, для их форматирования или вывода на экран используют внешние библиотеки, не учитывая при этом того факта, что в распоряжении программистов имеются JavaScript-команды для работы с консолями, которые встроены в браузеры. И обладают эти консоли гораздо более серьёзными возможностями, чем может показаться на первый взгляд.
Пожалуй, первое, что многим приходит в голову при слове «консоль» — это команда console.log() . Однако, она — лишь одна из многих подобных команд. Материал, перевод которого мы сегодня публикуем, посвящён особенностям работы с консолью JavaScript.
Что такое консоль?
Консоль JavaScript — это механизм, интегрированный в современные браузеры, который поддерживает встроенные инструменты разработки в интерфейсе, напоминающем командную строку. С использованием консоли разработчик может делать следующее:
- Просматривать журналы ошибок и предупреждений, возникающих на веб-странице.
- Взаимодействовать с веб-страницей, используя команды JavaScript.
- Отлаживать приложения и работать с DOM непосредственно из браузера.
- Исследовать и анализировать сетевую активность.
Методы console.log, console.error, console.warn и console.info
Вероятно, наиболее часто используемыми методами при работе с консолью являются console.log() , console.error() , console.warn() и console.info() . Этим методам можно передавать один или несколько параметров. Система вычисляет значение каждого из них и объединяет все результаты в строку, части которой разделены пробелами. В случае с объектами или массивами эти команды позволяют выводить их в таком виде, который позволяет просматривать их содержимое. Вот как это выглядит.
Использование различных команд для вывода данных в консоль
Метод console.group
Метод console.group() позволяет собирать серии вызовов console.log() (а также — других подобных методов) в группы, содержимое которых можно сворачивать и разворачивать. Пользоваться этим методом очень просто: после вызова console.group() (или после console.groupCollapsed() , если группу требуется вывести сразу в свёрнутом виде) нужно поместить все вызовы console.log() , которые надо сгруппировать. Затем, в конце набора команд, которые требуется сгруппировать, надо поместить команду console.groupEnd() . Рассмотри пример.
В консоль, после выполнения этого фрагмента кода, попадёт следующее.
Группировка данных в консоли с помощью метода console.group()
Метод console.table
После того, как я узнал о существовании метода console.table() , моя жизнь изменилась навсегда. Например, использование обычной команды console.log() при выводе JSON-кода или больших JSON-массивов — это сущий кошмар. Метод console.table() позволяет выводить сложные структуры данных внутри симпатичных таблиц, столбцам которых можно давать имена, передавая их в качестве параметров (не все браузеры поддерживают эту возможность console.table() ). Вот пример работы с этой командой.
То, что получилось, и выглядит отлично, и способно облегчить отладку.
Табличное оформление выводимых данных с помощью console.table()
Методы console.count, console.time и console.timeEnd
Методы console.count() , console.time() и console.timeEnd() можно назвать чем-то вроде швейцарского ножа для разработчика, который занимается отладкой приложений. Так, метод console.count() позволяет подсчитывать количество собственных вызовов и выводить его в консоль с заданной меткой. Метод console.time() позволяет запустить именованный таймер (имя передаётся ему в качестве параметра, на одной странице может присутствовать до 10000 таймеров). Для того чтобы остановить конкретный таймер, используется команда console.timeEnd() с меткой таймера, передаваемой в качестве параметра. Она останавливает таймер и выводит время его работы в консоль. Вот как пользоваться этими методами.
А вот как выглядит результат работы этого кода в консоли.
Использование методов console.count(), console.time() и console.timeEnd()
Методы console.trace и console.assert
Методы console.trace() и console.assert() позволят выводить из места их вызова информацию о стеке. Представьте себе, что вы занимаетесь разработкой JS-библиотеки и хотите сообщить пользователю о том, где возникла ошибка. В подобном случае эти методы могут оказаться весьма полезными. Метод console.assert() похож на console.trace() , разница между ними заключается в том, что console.assert() выведет данные лишь в том случае, если не выполнится переданное ему условие. Вот как работать с этими методами.
Несложно заметить, что вывод, генерируемый этим фрагментом кода, выглядит так же, как нечто подобное выглядело бы в React (или в любой другой библиотеке), когда фреймворк сообщает о возникновении исключения.
Результат использования команд console.assert() и console.trace()
Команды для работы с консолью и продакшн-код
Команды для работы с консолью нужны на этапе разработки и отладки приложений. Это значит, что когда придёт время выпуска продукта, эти команды придётся из кода удалить. Об этом можно просто забыть и, через некоторое время после сборки продакшн-версии проекта, заметить, что программа пишет что-то в консоль тогда, когда в этом нет никакой необходимости. Не стоит нагружать компьютеры ненужной работой, пусть и такой, казалось бы, незначительной, как вывод данных в консоль. В то же время, учитывая то, что команды для работы с консолью могут пригодиться в ходе доработки приложения, лучше всего не удалять их из исходного кода программы насовсем, а убирать их лишь из её продакшн-версии. Тут нам на помощь придут средства для сборки проектов. Так, я постоянно пользуюсь Webpack, и на работе, и в собственных проектах. Этот инструмент позволяет удалять все ненужные команды по работе с консолью (он способен отличить их от других команд) из продакшн-сборок с использованием uglifyjs-webpack-plugin.
Эта конфигурация очень проста, но она облегчает повседневную работу программиста и избавляет от проблемы забытых команд по работе с консолью.
Итоги
В этом материале мы рассказали о некоторых полезных командах для работы с консолью. Они позволяют, с помощью инструментов, являющихся частью современных браузеров, решать множество задач, которые сопутствуют разработке и отладке клиентских JavaScript-приложений.
Кажется, что все в начале пути разработчика отлаживали код при помощи console.log() . Я уверен, что и сейчас среди читателей найдётся человек, который сможет отладить код любой сложности при помощи вывода информации в консоль.
Признаюсь честно, я и сам достаточно долго просто выводил что-то в консоль, пытаясь понять, что пошло не так. Я понимал, что использовать breakpoint быстрее и продуктивнее, даёт больше возможностей, ну и в целом выглядит солиднее, но по-прежнему пользовался консолью.
Пример использования console.log для отладки.
В общем, эта статья должна облегчить ежедневную работу таким, как я, и тем, кто только постигает науку отладки JS. Здесь не будет хардкора, только самые основы.
Что такое breakpoint?
Во многих языках программирования, если не во всех, существует специальная команда, которая останавливает выполнение кода. При этом доступно состояние программы в момент остановки. Чуть более подробно можно почитать на Википедии.
Существует несколько способов установить точку остановки. Самый очевидный — это написать в коде ключевое слово debugger . После этого загрузка страницы остановится и Chrome подсветит вам строчку, в которой установлен debugger .
Пример использования ключевого слова debugger .
Как можно увидеть на скриншоте или на работающем прототипе (только не забудьте открыть Chrome DevTools), приложение не просто открылось в нужный момент, но и подсветило значение переменных, с которыми можно работать, например, введя их в консоль или как-то поработав с их значениями.
Но у этого способа установки breakpoint есть один очевидный недостаток: каждый раз при попытке что-то отобразить придётся пересобирать приложение. Поэтому в Chrome DevTools есть другой способ.
Установка breakpoint при помощи Chrome DevTools.
Если кликнуть на любую строчку кода прямо в Chrome DevTools, то она подсветится синим, и это аналогично использованию ключевого слова debugger , но при этом нет необходимости пересобирать проект.
Иногда на одной строчке кода можно делать точку остановки в нескольких местах. Такие места Chrome подсвечивает пиктограммами, как те, которые отображаются перед словами document и getElementById . По сути, сейчас код будет остановлен дважды. Но если вам необходимо остановиться только в одном месте, можно точку остановки отключить, просто кликнув на пиктограмму.
Остановка в случае изменения DOM
Теперь давайте рассмотрим пример посложнее. Допустим, на нашей странице что-то происходит и появляется ошибка. Чтобы разобраться, необходимо в инспекторе выбрать DOM-узел, за которым вы хотите наблюдать, и после этого кликнуть правой кнопкой «Break on => Subtree modification». После чего код остановится и подсветит нужную нам функцию.
Установка breakpoint на изменение DOM-элемента.
Типы точек остановки на DOM-узлах:
- Subtree modifications. Срабатывает, когда дочерний или выделенный узел DOM-дерева изменяется, удаляется или добавляется. Не срабатывает при изменении атрибутов.
- Attributes modifications. Срабатывает при изменении атрибутов текущей или дочерних нод.
- Node Removal. Срабатывает, когда выделенная нода удаляется.
Остановка после XHR/Fetch-ответа
Если что-то идет не так после ответа от сервера, то на этот случай есть инструмент для создания точки остановки. Перейдите во вкладку Sources и в правой колонке в раскрывающемся списке “XHR/Fetch breakpoint” добавьте новое условие для остановки.
Установка breakpoint на XHR/Fetch-событие.
Похожим образом можно остановить выполнение программы при срабатывании какого-либо события. Чуть ниже “XHR/Fetch breakpoint” находятся раскрывающиеся списки “Global Events” и “Event Listener Breakpoint”. В них находятся всевозможные события, которые могут произойти на странице, начиная от банального клика и заканчивая чем-то более экзотическим, вроде события message .
События с условиями
Чтобы не останавливать код каждый раз, когда интерпретатор доходит до точки остановки, можно добавить условия срабатывания breakpoint. Это чрезвычайно удобно делать в цикле.
Чтобы прописать условия срабатывания, необходимо кликнуть правой кнопкой по установленной breakpoint и выбрать “Edit breakpoint”. А дальше на родном Javascript можно написать условное выражение, и когда его результат будет true , выполнение программы остановится.
Добавление условия к срабатыванию точки остановки.
А что дальше?
Прелесть использования точки остановки вместо console.log заключается не только в том, что можно получить исчерпывающую информацию о состоянии в месте остановки, но и в том, что исполнение программы можно продолжить по шагам. Для этого необходимо использовать правую панель во вкладке Sources.
Панель управления отладкой.
При помощи этой панели можно:
- Продолжить исполнение программы. Выполнение скрипта продолжится до конца или до следующей точки остановки.
- Сделать шаг (выполнить следующую команду), не заходя в функцию. Выполнение просто перешагнет через функцию, оказавшись перед следующей.
- Сделать шаг. В отличие от предыдущего примера, здесь мы «заходим» во вложенные функции и шаг за шагом проходим по скрипту.
- Продолжить выполнение до завершения текущей функции. Выполнение кода остановится на самой последней строчке текущей функции. Этот метод применяется, когда мы случайно нажали предыдущую кнопку и зашли в функцию, но нам она не интересна и мы как можно скорее хотим из неё выбраться.
- Активировать/деактивировать все точки остановки. Эта кнопка не влияет на выполнение кода, она лишь позволяет массово включить/отключить точки остановки.
- Разрешить/запретить остановку выполнения в случае возникновения ошибки. Если опция включена и инструменты разработчика открыты, то любая ошибка в скрипте приостанавливает выполнение кода, что позволяет его проанализировать. Поэтому если скрипт завершается с ошибкой, открываем отладчик, включаем эту опцию, перезагружаем страницу и локализуем проблему.
Заключение
На этом инструменты отладки в Chrome DevTools не заканчиваются, это только вершина айсберга. Несмотря на то, что поначалу использовать breakpoint кажется сложнее, чем вывод информации в консоль, в долгосрочной перспективе это сэкономит много времени и откроет большие возможности.
Если тема работы с Chrome DevTools будет интересна, я могу написать ещё одну статью с более хардкорными практиками и методами. А на этом у меня всё, спасибо.
Пользуюсь отладчиком в Firefox. Трассирую скрипт. Не понимаю, как прервать выполнение скрипта, ушедшего, пример в бесконечный цикл? Где эта кнопка? А то приходится закрывать браузер с зависшим скриптом и открывать заново.
Простой 10 комментариев
Вы не поняли. У меня есть точки останова. Я там себе что-то трассирую по ним, и в какой-то момент я захотел прервать скрипт полностью. Как это сделать? Т.е. не паузу сделать на очередном останове, а именно то, что называется "Ctrl-C". Прервать выполнение и выйти к чертям.
Николай Чуприк, пкм по вкладке, менеджер вкладок/окон, закрываешь вкладку или остонавливаешь процесс (точнее ге помню).
Если скрипт зависнет, то FF должен показать вам окно о том, что его надо остановить. Вы можете понизить время ожидания результата выполнения скрипта.
Я не нашёл в гугле как остановить скрипт. Может быть такого и нет, но если вы не знаете, что у вас отъедает CPU, попробуйте занизить время с 10 секунд до, допустим, 5 и запустите ваш скрипт через профайлер, он, возможно, покажет какой код тормозит.
Мне не понятно, что у вас происходит. Цикл может быть бесконечным, это не страшно, страшно, что окно не отвечает - это означает, что не хватает CPU ресурсов и либо вы делаете троттлинг вашего скрипта, либо перекладываете вычислительную работу на кого-либо (сервер, воркер, что у вас там).
Vest, что удивительно, я тоже ничего не нагуглил. Похоже прерывание скрипта невозможно. Вот только не могу понять причин этого, но она наверное должна быть, почему так сделали. Ещё надо заметить, что я использую Vue для интерфейса в браузере. Я не спец, но может это как-то связано с рендером. Типа пока не вернутся вычисления, не может закончить работу с DOM. Но с другой стороны там Virtual DOM.
А я сейчас отлаживаю скрипт, проводящий вычисления на клиенте. Там довольно навороченный рекурсивный алгоритм. Отлаживаю и. иногда он не выходит из рекурсии, всё зависает. Приходится закрывать окно, а потом заново всё открывать. Но скрипт на самом деле очень быстрый (если правильно работает), т.е. сложный алгоритмически, но не загрузный по времени. Воркеры или сервер не нужен.
За ссылочку про понижение времени — спасибо.
Николай Чуприк, меня закидают тапками, но я думаю, что всё дело в однопоточности JavaScript или конкретной вкладки. Когда у вас единственный поток занят рекурсивными вычислениями, то у него нет эдакого кванта времени, чтобы обработать ещё нажатие несуществующей кнопки Stop в вашем отладчике. Наверняка, это всё крутится в одном потоке, а если и нет, то возможно этот поток не отпущен браузером, чтобы принять вашу команду.
Я бы предложил вам добавить проверку на некоторую глобальную переменную, и если она "true", выйти из рекурсии, но смешно то, что JavaScript-консоль скорее всего зависнет и вы не сможете в ней выставить вашу переменную в "true"
¯\_(ツ)_/¯
Vest, JS поток конечно один, но для JS браузер является супервизором. Мне не понятно, почему он не может его тормознуть. Или кнопки Developer Tools реализованы в том же потоке JS :). Странно конечно.
Мне сейчас приходится ставить в рекурсию окно confirm('Стоп?'), и пока долбишь "нет", рекурсия продолжается, что неудобно. Причём если ставить breakpoint в рекурсивную функцию, то я могу всего лишь остановить выполнение скрипта, но не прервать его. После breakpoint у меня выбора нет, могу только продолжить выполнение, даже если понимаю, что это уход рекурсии в нирвану.
Николай Чуприк, хорошая идея. Пусть у вас будет счётчик в рекурсии. Вы будете каждый раз его увеличивать, когда куда-либо проваливаетесь. Как наберёте 100 вызовов - показываете confirm, а потом ещё раз 100 циклов и т. д.
p.s. или не прерывать, а вызывать debugger.
Vest, помыкавшись, понял, что понизить время ожидания отклика скрипта — самый нормальный выход из ситуации. Напишите, пожалуйста, ответом, чтобы я отметил решение.
Как вы и просили, Николай Чуприк, я решил написать ответ:
Если скрипт зависнет, то FF должен показать вам окно о том, что его надо остановить. Вы можете понизить время ожидания результата выполнения скрипта.
За это отвечает параметр: dom.max_script_run_time
В других браузерах я не нашёл подобной опции. Зато мне понравилась цитата отсюда:
“JavaScript that executes for whole seconds is probably doing something wrong. ” says Brendan Eich, creator of JavaScript.
update 03.07.2018: я решил почитать, что новенького в Хроме 67. Посмотрите, пожалуйста, наверное это то, что вы просили: What's New In DevTools (Chrome 67) - Stop infinite loops.
Спасибо большое, Vest. Вот только не понятно, что мешает просто сделать кнопку Stop? Зачем такое неочевидное выпадающее меню? Кто там придумывает UX у них!? —Уволить!
тупой ответ - поискать кнопку паузы. Не допускать ухода в бесконечный цикл. Иначе ваш аппарат никогда не приземлится на Марсе
Пауза — паузой. Нажал я паузу, а что дальше? У меня только одна возможность — нажать снова play. А рекомендация не допускать ухода в бесконечный цикл сродни совету написать скрипт сразу без ошибок, чтобы не пришлось его отлаживать.
Николай Чуприк, вот либо у меня огнелис какой-то хитрый, либо одно из двух. Кнопки трассировки находятся рядом с кнопкой паузы, дублируются клавиатурой F10 - выполнить процедуру, не заходя в нее, F11 - зайти в процедуру (при возможности), Shift-F11 - продолжить выполнение до выхода из текущей процедуры. Чтобы дальше запустить - F8. Эти же самые кнопки работают в хроме. Между прочим, отладчик в хроме намного более информативный, в частности, можно находить DOM events, то есть обработчики, повешенные на элементы программно, чего в огнелисе мне, например, сильно не хватало. Я уже полгода в огнелисе ничего не отлаживаю, поскольку считаю его бесперспективным, особенно после ноябрьских кульбитов с обновами, когда они мне всю работу поломали, в частности, перестал работать GreasyMonkey. Держу его так, по привычке.
А рекомендация не допускать ухода в бесконечный цикл - всего лишь намек на правильное написание программы.
marsdenden, ну тут дело вкуса. Меня вот в FF бесит, что я не могу зафиксировать конфигурацию "аккордеонов" в области где Watch, Breakpoints, Call Stack и всё вот это. При запуске новой отладки FF их все разворачивает, меняя рабочее простраyство. И каждый запуск скрипта его надо настраивать. Более того, на каждой точке останова он меняя рабочее пространство.
Читайте также: