События load, error, атрибуты async, defer

На предыдущем занятии мы узнали о событиях, которые происходят во время загрузки HTML-документа:

DOMContentLoaded, load, unload, beforeunload

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

img, script

Оказывается для всех элементов, имеющих атрибут src, генерируется событие

load

при завершении загрузки содержимого элемента. Например, повесим обработчик такого события на загрузку изображения:

<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body>
<p><img id="image" src="/htm/javascript_dom/files/images/winter.jpg?nocashe=1" />
<script>
image.onload = function(event) {
       console.log(`Изображение: ${image.offsetWidth}x${image.offsetHeight}`);
}
</script>
</body>
</html>

Теперь после загрузки изображения мы увидим в консоли его размеры. То же самое можно сделать и со скриптом:

<script src="js/ex1.js" onload="execute()"></script>

А в скрипте выше прописать эту функцию:

function execute() {
     console.log("скрипт загружен");
}

При обновлении документа мы увидим сообщение, что скрипт загружен. Для чего нам может понадобиться обработчик события load для скриптов? Например, если нужно вызвать некоторую функцию, объявленную в ex1.js. Очевидно, это можно сделать только после его загрузки:

function execute() {
         __fromJS();
}

Причем, во всех случаях обработчик execute() должен быть объявлен до строчки

<script src="js/ex1.js" onload="execute()"></script>

Или же, мы можем сделать так: создать скрипт динамически:

let script = document.createElement('script');
script.src = "js/ex1.js";
script.onload = execute;
document.head.append(script);

И тогда местоположение обработчика не будет иметь значения.

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

script.src = "js/ex11.js";

то при загрузке произойдет ошибка и событие load не сгенерируется для данного элемента. Для обработки таких ошибок можно использовать событие

error

следующим образом:

script.onerror = execute_error;

и пропишем обработчик:

function execute_error() {
     console.log("ошибка при загрузке ресурса");
}

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

Аналогичный обработчик можно повесить и для изображения:

image.onerror = () => console.log("error load image");

async, defer

Во второй части занятия поговорим об атрибутах тега <script>. Они добавляют гибкости при подключении внешних скриптов к HTML-документу. И начнем с атрибута defer. Представьте, что у нас имеется внешний скрипт, который имеет большой объем и долго загружается с сервера:

<p>Текст до скрипта
<script src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта

Тогда мы увидим сначала первый абзац и только после загрузки – второй абзац. То есть, наш HTML-документ будет подвисать и ждать окончания загрузки внешнего ресурса. Если у нас всего один такой внешний скрипт, то в самом простом случае его можно просто расположить в конце документа:

<p>Текст до скрипта
<script defer src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта

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

Если добавить этот атрибут в наш тег:

<p>Текст до скрипта
<script defer src="http://funnyballs.ru/long_script.js">
</script>
<p>Текст после скрипта

то он выполнится до появления какого-либо абзаца, так как браузер загрузит и выполнит его быстрее, чем отобразит теги p. Это как раз и есть эффект атрибута defer – браузер обрабатывал скрипт в фоне. Причем, скрипты с этим атрибутом выполнятся только после того, как браузер целиком загрузит всю страницу и построит DOM-дерево, но до возникновения события DOMContentLoaded. Мы это можем увидеть на следующем примере:

<script>
document.addEventListener('DOMContentLoaded', function() {
            console.log("DOMContentLoaded");
});
</script>

Далее, если на нашей странице имеется несколько скриптов с атрибутом defer:

<script defer src=" http://funnyballs.ru/long_script.js"></script>
<script defer src="js/ex2.js"></script>

то они будут выполняться в порядке записи в HTML-документе, то есть, сначала выполнится ex1.js, а затем, ex2.js вне зависимости от того, который из них будет загружен раньше. Например, если скрипт ex1.js окажется большим, а ex2.js – маленьким, то выполнение второго скрипта будет отложено до тех пор, пока не загрузится первый большой скрипт и не выполнится.

Иногда такое поведение не то, что нам бы хотелось. Допустим, что для нас было бы лучше, если бы скрипты запускались в порядке их загрузки. В этом случае следует воспользоваться атрибутом

async

который делает скрипт совершенно независимым от других скриптов, а также от события DOMContentLoaded: оно может наступить и раньше и позже, в зависимости от процесса загрузки скрипта.

Такие асинхронные скрипты очень полезны для добавления на страницу сторонних скриптов: счётчиков, рекламы и т.п. Они не зависят от других скриптов, и не вызывают задержек при загрузке и отображении всего HTML-документа.

Кстати, если в JavaScript-программе скрипт создается динамически, как в одном из наших примеров:

let script = document.createElement('script');
script.src = "js/ex1.js";
script.onload = execute;
document.head.append(script);

то он запускается в асинхронном режиме.

Вот так работают атрибуты

async, defer

и события

load, error

для отслеживания загрузки ресурсов отдельных элементов.

Видео по теме