Как работает async в скриптах: примеры и инструкции
Асинхронная загрузка скриптов с атрибутом async
Атрибут async для тега <script> позволяет браузеру загружать скрипт параллельно с парсингом HTML, а выполнить его сразу после получения, не дожидаясь окончания разбора документа. Это ускоряет загрузку страницы, но меняет порядок выполнения. Рассмотрим основной подход и альтернативы.
Как сделать загрузку скрипта независимой от парсинга документа?
Добавьте атрибут async к тегу <script>:
<script async src="analytics.js"></script>Js script async (асинхронный скрипт (script async))
Браузер начнёт загрузку analytics.js сразу, не останавливая чтение HTML. Скрипт выполнится в момент, когда файл будет получен - возможно, до завершения парсинга, возможно, после. Это подходит для независимых скриптов (счётчики, виджеты).
Типичная ошибка: скрипт обращается к DOM, которого ещё нет. Решение - проверять существование элемента или оборачивать логику в событие DOMContentLoaded внутри самого скрипта.
Проблема: потеря порядка выполнения, если нужно несколько скриптов с зависимостями. Async не гарантирует последовательность.
Как загрузить скрипт после полной загрузки DOM, сохраняя порядок?
Используйте атрибут defer:
<script defer src="jquery.js"></script>
<script defer src="app.js"></script>
Скрипты загружаются параллельно, но выполняются строго по порядку после того, как весь HTML разобран, перед событием DOMContentLoaded.
Ошибка: путать async и defer. Defer подходит, когда скрипты зависят друг от друга или от DOM.
Как управлять загрузкой скрипта из JavaScript динамически?
Создайте элемент <script> и установите атрибут async в true или false:
const script = document.createElement('script');
script.src = 'widget.js';
script.async = true;
document.body.appendChild(script);
По умолчанию динамически созданные скрипты имеют async = true. Чтобы отключить, явно укажите script.async = false.
Проблема: скрипт может выполниться до того, как загрузятся другие ресурсы. Рекомендуется добавлять скрипты после события DOMContentLoaded, если они манипулируют DOM.
Как использовать async с модулями ES6?
Модульные скрипты (type="module") по умолчанию ведут себя как defer. Если добавить async, они будут выполняться сразу после загрузки, независимо от порядка:
<script type="module" async src="app.mjs"></script>
Это редко применяется, так как модули обычно содержат зависимости.
Ошибка: полагаться на порядок выполнения async-модулей - он не гарантирован.
Расширенные примеры использования async
Пример 1. Неопределённый порядок выполнения нескольких async-скриптов
<script async src="script1.js"></script>
<script async src="script2.js"></script>
<script async src="script3.js"></script>
В файлах:
// script1.js
console.log('Скрипт 1');
// script2.js
console.log('Скрипт 2');
// script3.js
console.log('Скрипт 3');
Результат может быть любым, например:
Скрипт 2 Скрипт 1 Скрипт 3
Порядок зависит от скорости загрузки каждого файла. Для зависимых скриптов используйте defer или объединяйте в один файл.
Пример 2. Async скрипт и событие DOMContentLoaded
<script>
document.addEventListener('DOMContentLoaded', () => {
console.log('DOM готов');
});
</script>
<script async src="late.js"></script>
<!-- late.js: console.log('Async скрипт'); -->
Если late.js загрузится быстро, его выполнение может произойти до события DOMContentLoaded. При медленной загрузке - после. Результат:
(Возможный вариант 1) Async скрипт DOM готов (Вариант 2) DOM готов Async скрипт
Это важно для скриптов, которые должны гарантированно работать с готовым DOM - в них нужно внутри проверять состояние документа или слушать событие.
Пример 3. Async и document.write (не делайте так)
<script async>
document.write('<p>Новый текст</p>');
</script>
После того как HTML разобран, document.write перезаписывает весь документ. Скрипт с async может выполниться в любой момент, что приведёт к потере содержимого страницы.
Результат: пустая страница или неожиданное содержимое. Используйте innerHTML или DOM-методы.
Пример 4. Обработка ошибок загрузки async-скрипта
<script async src="https://example.com/tracker.js"
onerror="console.error('Не удалось загрузить скрипт')"></script>
Событие onerror срабатывает, если ресурс недоступен. Это полезно для аналитики - можно попробовать альтернативный источник.
Не удалось загрузить скрипт
Пример 5. Async с IntersectionObserver для ленивой загрузки
<script async src="lazyload.js"></script>
<!-- в lazyload.js: -->
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));
Async скрипт выполняется быстро, но если изображений ещё нет в DOM (из-за позднего выполнения), селекторы ничего не найдут. Решение - запустить наблюдение после DOMContentLoaded внутри скрипта:
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initObserver);
} else {
initObserver();
}
Пример 6. Комбинация async с defer (не имеет смысла)
Если указать оба атрибута, async имеет приоритет. Браузер проигнорирует defer. Проверка:
<script async defer src="test.js"></script>
Файл test.js выполнится асинхронно, не дожидаясь окончания парсинга.