Асинхронная загрузка CSS и JavaScript
Асинхронная загрузка внешних CSS- и JS-ресурсов способствует увеличению скорости загрузки сайта и отображения веб-страниц в браузере, обеспечивая загрузку и выполнение файлов в фоновом режиме без блокировки рендеринга
Содержание
Прежде, чем приступать к реализации асинхронной загрузки CSS- и JS-файлов, рекомендуем ознакомиться со значением структуры кода веб-страниц по отношению к скорости загрузки сайта.
Что такое асинхронная загрузка?
Процесс отображения страницы сайта в браузере сопровождается задержками (Render Blocking) всякий раз, когда браузер обнаруживает внешние CSS- и JS-ресурсы в тегах link и script соответственно. Это приводит к тому, что некоторое время пользователь находится в ожидании отображения веб-страницы на экране до момента, пока не загрузятся и не выполнятся файлы, препятствующие её отображению.
Например, блок head веб-страницы имеет следующее содержание:
Во избежание задержки отображения страницы применяется асинхронная загрузка файлов CSS и JavaScript, обеспечивающая безостановочный процесс отображения страницы в браузере.
Асинхронная загрузка JavaScript
Тег SCRIPT без атрибутов (синхронная загрузка JS)
Задержка рендеринга страницы в браузере обеспечивает выполнение скриптов в порядке их указания в HTML-коде.
Тег SCRIPT с атрибутом ASYNC (асинхронная загрузка JS)
Атрибут async обеспечивает асинхронную загрузку внешнего JS-файла: файл будет загружен и выполнен в фоновом режиме без задержки отображения страницы.
При применении тегов script с атрибутом async следует помнить, что порядок выполнения скриптов (если JS-файлов несколько) не сохраняется: они будут выполняться по окончании их загрузки вне зависимости от их порядка в HTML-коде. Это может привести к ошибкам и несрабатыванию скриптов.
Решением является объединение кода всех JS-файлов в один, или применение атрибута defer, если проблема касается только внешних файлов.
Тег SCRIPT с атрибутом DEFER (отложенная загрузка JS)
Атрибут defer обеспечивает отложенную загрузку внешнего JS-файла: файл будет загружен без задержки отображения страницы и выполнен по окончанию рендеринга.
Следует учитывать, что атрибут defer (как и async ) не откладывает выполнение встроенных в страницу скриптов: они будут проанализированы браузером и в случае, если файл JS-библиотеки, которую они используют, не будет загружен и исполнен, то и скрипты будут проигнорированы:
Для сайтов на CMS
Асинхронная загрузка CSS
Загрузка и обработка браузером внешних файлов стилей также сопровождается блокировкой рендеринга страницы, избежать которую можно посредством их асинхронной загрузки. Сложность в том, что реализовать асинхронную загрузку CSS с помощью атрибутов тега, как это делается для JS-файлов, нельзя. Решить поставленную задачу можно различными способами с помощью JavaScript.
Посредством JS
Обеспечить асинхронную загрузку внешних CSS-файлов можно с помощью JavaScript без применения сторонних библиотек.
Для этого можно использовать следующую JS-функцию:
После объявления функции в коде нужно её вызвать соответствующее количеству CSS-файлов число раз:
Для применения данного способа разместите функцию и её вызов(ы) в теге script перед закрывающим тегом body :
С помощью jQuery
Если на странице применяется JS-библиотека jQuery, то для асинхронной загрузки CSS-файла можно применить следующий код:
Если требуется асинхронно загрузить несколько файлов стилей, нужно перечислить их в JS-функции:
При этом, если JS-файл с jQuery будет загружаться асинхронно (с применением атрибута async или defer ), то скрипт асинхронной загрузки CSS также не сработает, т. к. парсер браузера уже минует данный скрипт, не имея данных для его выполнения (jQuery в этот момент будет только на стадии загрузки):
Чтобы асинхронно загрузить JavaScript (jQuery) и CSS, можно применить следующий приём:
С помощью скрипта headJS
Существует ряд скриптов, созданных для реализации асинхронной загрузки как CSS-файлов, так и JS-файлов. Одним из таких скриптов является headJS.
Для его применения нужно скачать JS-файл и подключить его к странице:
Все перечисленные файлы будут асинхронно загружены и выполнены в указанном порядке.
Чтобы загружать асинхронно в том числе скрипт headJS, можно применить следующий способ:
Т. к. все перечисленные способы реализации асинхронной загрузки CSS-файлов реализуются посредством JavaScript, для подстраховки в код желательно вставить ссылки на эти файлы в теге noscript на случай, если браузер не выполняет JS-код:
Отложенная загрузка стилей
Каждый из предложенных способов откладывает загрузку стилей, в результате сперва происходит отрисовка страницы без их применения, после чего стили резко применяются, преображая страницу.
Для сайтов на CMS
Обеспечить асинхронную загрузку CSS для сайтов на Joomla, WordPress, Magento и Drupal можно с помощью платной версии плагина JCH Optimize.
Плагин JCH Optimize Pro способен:
Выводы и заключение
Асинхронная загрузка:
В этой статье разберем, что такое JavaScript и для чего он нужен. Кроме этого, рассмотрим способы подключения кода JavaScript (сценариев) к странице.
Что такое JavaScript
Потребность в создании языка программирования для браузера появилась ещё в 90-е годы. В это время на веб-страницах хотелось делать уже больше, чем просто выводить статичный контент.
В 1996 году язык JavaScript был стандартизован компанией Ecma, которая занимается стандартизацией информационных и коммуникационных технологий. Сама спецификация была названа ECMAScript или сокращённо ES. По сути, JavaScript является реализацией спецификации ECMAScript. Новые версии ECMAScript выходят ежегодно и добавляют в язык новые возможности.
В настоящее время язык JavaScript уже применяется не только для веба. С помощью него можно написать обычные приложения для десктопных и мобильных операционных систем, использовать его в роли серверного языка (node.js) и др.
Виды браузеров и браузерных движков
Основные современные движки и браузеры, которые их используют:
Как осуществляется подключения кода JavaScript к странице
С помощью этого способа вы можете подключить JavaScript код к большому количеству HTML страниц. В этом случае при изменении кода скрипта не придётся его редактировать на каждой странице, к которой он подключён.
Если подключить скрипт с помощью атрибута src и дополнительно ещё указать некоторый код между открывающим и закрывающим тегом script этого элемента, то код, который указали непосредственно, будет проигнорирован, т.е. он не выполниться.
Как выполняются скрипты на странице
В этом примере используются 2 метода JavaScript:
На этом этапе увидим следующую картинку:
Атрибуты async и defer
Второе отличие от async заключается в том, что будет сохранена очерёдность выполнения скриптов.
Асинхронная загрузка JavaScript, CSS: что это, скрипты и настройка
Асинхронная загрузка – это загрузка, которая позволяет браузеру загружать основной HTML и прочие ресурсы без ожидания загрузки стилей CSS и JavaScript, что существенно ускоряет работу сайта.
Проще говоря, пользователь, посещая ресурс, не дожидается последовательной загрузки всех скриптов и стилей, они подгружаются в конце. Тем самым не тормозя процесс загрузки HTML, который загружается последовательно (строка за строкой) и может долго подгружаться на CSS и JavaScript.
Синхронная загрузка JavaScript
По умолчанию JS файлы загружаются следующим образом:
Асинхронная загрузка скрипта HTML5
Использование HTML5 позволяет сделать асинхронную загрузку скриптов, благодаря чему страница будет загружаться в разы быстрее. Достаточно для файла JS добавить атрибут defer либо async.
Чем отличаются атрибуты async и defer?
Применение этих атрибутов в конечном итоге обеспечивает нам загрузку скриптов асинхронным способом. Но зачем тогда создавать два атрибута? Дело в том, что они отличаются выполнением скрипта.
Скрипт, имеющий атрибут defer, будет выполняться относительно других скриптов как и прежде, исключительно после окончательной загрузки и парсинга веб-страницы, но до события загрузки дополнительных ресурсов DOMContentLoaded из document.
При использовании async, скрипт выполнится моментально после его полной загрузки и до загрузки window.
На заметку. Данный механизм актуален не для всех браузеров, в частности, Internet Explorer. Еще он не функционирует, если в script.js есть строки document.write.
Загрузка JavaScript асинхронно скриптом от Google
В отличие от других поисковых систем, Google уделяет огромное внимание скорости загрузки веб-ресурсов. Из-за этого даже качественному сайту, если он загружается медленно, оказаться в ТОПе будет практически нереально.
Во избежание проблем с загрузкой, Google разработал и предложил вебмастерам собственный скрипт, позволяющий загружать JS непоследовательно.
Для его использования достаточно заменить
Но метод также подходит не для всех, потому что опять-таки неактивен в файлах с document.write.
Универсальная асинхронная загрузка JS
А вот вариант, подходящий всем браузерам (кроме IE 6 и старее, которыми уже практически никто не пользуется), сервисов и даже для document.write.
Во фрагменте страницы, предназначенном для отображения элемента, пропишите блок div и оставьте его пустым:
Что такое defer async
JavaScript является неотъемлемой частью любого современного веб-приложения, и стратегии, которые мы решаем использовать для загрузки, напрямую влияют на производительность работы этого самого приложения. В данной статье мы попробуем понять важные различия между каждым подходом, плюсы и минусы наряду с последствиями производительности и способы оптимизации по взаимодействию со страницей и времени загрузки.
Для демонстрации я создам веб-сайт, состоящий из следующих внешних зависимостей. Обратите особое внимание на соответствующие размеры файлов, так как время загрузки файлов прямо пропорционально этому.
Подход-1 [скрипты в разделе head]
В первом случае мы загрузим все теги scripts в раздел head нашего HTML. Ниже приведен скриншот анализа сетевой вкладки chrome страницы, к готовой для взаимодействия с пользователем.
Последовательность выполнения кода различных JS-файлов будет сохранена в том порядке, в котором файлы были включены в HTML. В текущем примере, даже если file2 и file3 были загружены до file1, порядок выполнения будет правильным.
В этом сценарии синтаксический анализ HTML будет приостановлен до тех пор, пока все 3 скрипта в разделе head не будут загружены, проанализированы и выполнены. Пустой белый экран будет показан пользователю, даже если HTML-файл уже был загружен [но не проанализирован]. Это, безусловно, не есть хорошо для юзабилити.
Ни один из вышеперечисленных скриптов не сможет получить доступ / манипулировать HTML-страницей, так как DOM еще не готов. Одним из возможных решений для обработки этой проблемы является прослушивание события DOMContentLoaded, а затем выполнение кода после этого. DOMContentLoadedСобытие срабатывает, когда исходный HTML-документ был полностью загружен и проанализирован, не дожидаясь завершения загрузки таблиц стилей, изображений.
Подход-2 [скрипты в конце]
Чтобы преодолеть 2 проблемы, с которыми мы сталкиваемся в первом подходе, давайте загрузим все 3 скрипта в нижней части тега body.
Плюсы: HTML анализируется перед загрузкой скриптов, так что пользователь будет иметь возможность видеть фактическое содержание сразу вместо того, чтобы ждать скриптов.
Так как все скрипты выполняются после разбора HTML, то все они могут получить доступ к DOM для любых манипуляций. Последовательность выполнения скриптов сохраняется.
Нет прироста производительности как такового.
Подход-3 [использование атрибута Async]
HTML5 представил async атрибут script, который помогает в загрузке соответствующих файлов скрипта параллельно на другой поток, не влияя на синтаксический анализ HTML.
Тем не менее, соответствующий сценарий будет проанализирован и выполнен, как только он завершит загрузку, независимо от того, завершен ли или нет синтаксический анализ HTML, и будет иметь ссылку на элемент DOM до этой конкретной точки.
Здесь вы можете четко увидеть, что file2.js был загружен до HTML файла. Однако, в то время как браузер загружает file2, он не приостановил синтаксический анализ HTML и из — за этого, ко времени его выполнения-он имел ссылку на заполнитель html, чтобы ввести динамическое содержимое.
Плюсы: Поскольку скрипты загружаются в другом потоке, синтаксический анализ HTML не будет приостановлен, и пользователь сможет видеть непосредственный контент вместо белого пустого экрана. Основной прирост производительности, т. е. DOMContentLoaded время уменьшилось с 47.68 секунд до всего 21.12 секунд и составляет
Что произойдет, если JS загружается до того, как DOM элемент будет доступен?Будет выброшена выброшена ошибка.
Примечание: размещение скриптов с атрибутом async в нижней части раздела body будет бесполезным и эквивалентным подходу-2.
Подход-4 [использование атрибута Defer]
Defer атрибут заставит скрипт выполниться только после того как парсинг HTML был завершен. Один очень важный момент, который следует учитывать здесь, заключается в том, что Chrome еще не поддерживает отсрочку и не оказывает влияния на продолжительность DOMContentLoaded. Однако он выполняет скрипты в конце синтаксического анализа HTML.
Последовательность импорта скриптов сохраняется. Итак, file3.js выполняется только после завершения загрузки и выполнения file1, даже если file3 был загружен ранее.
Поддержка браузеров- он имеет лучшую поддержку браузеров по сравнению с атрибутом asynс, т. е. частично поддерживается в IE v6-9
Скрипты могут получить доступ к DOM, так как он выполняется только после разбора полного HTML.
Первоначально я думал, что отсрочка будет лучшим выбором, чем асинхронность, но позже обнаружил, что Chrome еще не поддерживает его [версия 71.0.3578.98] и не оказывает влияния на продолжительность DOMContentLoaded.
Тем не менее, он работает так, как ожидалось, в Firefox со значительным улучшением производительности.
Предпочтительнее размещать теги скриптов в разделе head с атрибутом async для сторонних библиотек, которые зависят от Google Analytics, Google reCAPTCHA или чего-либо еще, что не требует DOM-доступа, поскольку соответствующие скрипты загружаются параллельно, но выполняются немедленно.
Используйте defer для всех других скриптов, загруженных в разделе head, поскольку они также будут загружаться параллельно, но будут выполняться только после завершения синтаксического анализа HTML и DOM готов к доступу/манипуляции.
Вы также можете использовать сочетание DOMContentLoaded listener внутри асинхронных скриптов для выполнения функциональности позже. Пожалуйста, оставьте свои мнения и выводы в комментариях, и я буду рад обсудить их с вами.

Разбираем Async/Await в JavaScript на примерах
Автор статьи разбирает на примерах Async/Await в JavaScript. В целом, Async/Await — удобный способ написания асинхронного кода. До появления этой возможности подобный код писали с использованием коллбэков и промисов. Автор оригинальной статьи раскрывает преимущества Async/Await, разбирая различные примеры.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
Skillbox рекомендует: Образовательный онлайн-курс «Java-разработчик».
Callback
Callback представляет собой функцию, вызов которой отложен на неопределенное время. Раньше обратные вызовы использовались в тех участках кода, где результат не мог быть получен сразу.
Вот пример асинхронного чтения файла на Node.js:
Проблемы возникают в тот момент, когда требуется выполнить сразу несколько асинхронных операций. Давайте представим себе вот такой сценарий: выполняется запрос в БД пользователя Arfat, нужно считать его поле profile_img_url и загрузить картинку с сервера someserver.com.
После загрузки конвертируем изображение в иной формат, например из PNG в JPEG. Если конвертация прошла успешно, на почту пользователя отправляется письмо. Далее информация о событии заносится в файл transformations.log с указанием даты.
Стоит обратить внимание на наложенность обратных вызовов и большое количество >) в финальной части кода. Это называется Callback Hell или Pyramid of Doom.
Недостатки такого способа очевидны:
Положительным моментом промисов стало то, что с ними код читается гораздо лучше, причем сверху вниз, а не слева направо. Тем не менее у промисов тоже есть свои проблемы:
Предположим, что есть цикл for, выводящий последовательность чисел от 0 до 10 со случайным интервалом (0–n секунд). Используя промисы, нужно изменить этот цикл таким образом, чтобы числа выводились в последовательности от 0 до 10. Так, если вывод нуля занимает 6 секунд, а единицы — 2 секунды, сначала должен быть выведен ноль, а потом уже начнется отсчет вывода единицы.
Async-функции
Добавление async-функций в ES2017 (ES8) упростило задачу работы с промисами. Отмечу, что async-функции работают «поверх» промисов. Эти функции не представляют собой качественно другие концепции. Async-функции задумывались как альтернатива коду, который использует промисы.
Async/Await дает возможность организовать работу с асинхронным кодом в синхронном стиле.
Таким образом, знание промисов облегчает понимание принципов Async/Await.
В обычной ситуации он состоит из двух ключевых слов: async и await. Первое слово и превращает функцию в асинхронную. В таких функциях разрешается использование await. В любом другом случае использование этой функции вызовет ошибку.
Async вставляется в самом начале объявления функции, а в случае использования стрелочной функции — между знаком «=» и скобками.
Эти функции можно поместить в объект в качестве методов либо же использовать в объявлении класса.
NB! Стоит помнить, что конструкторы класса и геттеры/сеттеры не могут быть асинхронными.
Семантика и правила выполнения
Async-функции, в принципе, похожи на стандартные JS-функции, но есть и исключения.
Так, async-функции всегда возвращают промисы:
В частности, fn возвращает строку hello. Ну а поскольку это асинхронная функция, то значение строки обертывается в промис при помощи конструктора.
Вот альтернативная конструкция без Async:
В этом случае возвращение промиса производится «вручную». Асинхронная функция всегда обертывается в новый промис.
В том случае, если возвращаемое значение — примитив, async-функция выполняет возврат значения, обертывая его в промис. В том случае, если возвращаемое значение и есть объект промиса, его решение возвращается в новом промисе.
Но что произойдет в том случае, если внутри асинхронной функции окажется ошибка?
Если она не будет обработана, foo() вернет промис с реджектом. В этой ситуации вместо Promise.resolve вернется Promise.reject, содержащий ошибку.
Async-функции на выходе всегда дают промис, вне зависимости от того, что возвращается.
Await влияет на выражения. Так, если выражение является промисом, async-функция приостанавливается до момента выполнения промиса. В том случае, если выражение не является промисом, оно конвертируется в промис через Promise.resolve и потом завершается.
А вот описание того, как работает fn-функция.
Решаем задачу
Ну а теперь давайте рассмотрим решение задачи, которая была указана выше.
Вот решение с выводом чисел, здесь есть два варианта.
А вот решение с использованием async-функций.
Необработанные ошибки обертываются в rejected промис. Тем не менее в async-функциях можно использовать конструкцию try/catch для того, чтобы выполнить синхронную обработку ошибок.
canRejectOrReturn() — это асинхронная функция, которая либо удачно выполняется (“perfect number”), либо неудачно завершается с ошибкой (“Sorry, number too big”).
Поскольку в примере выше ожидается выполнение canRejectOrReturn, то собственное неудачное завершение повлечет за собой исполнение блока catch. В результате функция foo завершится либо с undefined (когда в блоке try ничего не возвращается), либо с error caught. В итоге у этой функции не будет неудачного завершения, поскольку try/catch займется обработкой самой функции foo.
Стоит уделить внимание тому, что в примере из foo возвращается canRejectOrReturn. Foo в этом случае завершается либо perfect number, либо возвращается ошибка Error (“Sorry, number too big”). Блок catch никогда не будет исполняться.
Проблема в том, что foo возвращает промис, переданный от canRejectOrReturn. Поэтому решение функции foo становится решением для canRejectOrReturn. В этом случае код будет состоять всего из двух строк:
А вот что будет, если использовать вместе await и return:
В коде выше foo удачно завершится как с perfect number, так и с error caught. Здесь отказов не будет. Но foo завершится с canRejectOrReturn, а не с undefined. Давайте убедимся в этом, убрав строку return await canRejectOrReturn():
Распространенные ошибки и подводные камни
В некоторых случаях использование Async/Await может приводить к ошибкам.
Такое случается достаточно часто — перед промисом забывается ключевое слово await:
В коде, как видно, нет ни await, ни return. Поэтому foo всегда завершается с undefined без задержки в 1 секунду. Но промис будет выполняться. Если же он выдает ошибку или реджект, то в этом случае будет вызываться UnhandledPromiseRejectionWarning.
Async-функции в обратных вызовах
Нам нужны аккаунты ArfatSalman, octocat, norvig. В этом случае выполняем:
Чрезмерно последовательное использование await
В качестве примера возьмем такой код:
Здесь в переменную count помещается число репо, затем это число добавляется в массив counts. Проблема кода в том, что пока с сервера не придут данные первого пользователя, все последующие пользователи будут находиться в режиме ожидания. Таким образом, в единый момент обрабатывается лишь один пользователь.
Promise.all на входе получает массив промисов с возвращением промиса. Последний после завершения всех промисов в массиве или при первом реджекте завершается. Может случиться так, что все они не запустятся одновременно, — для того чтобы обеспечить одновременный запуск, можно использовать p-map.
Заключение
Async-функции становятся все более важными для разработки. Ну а для адаптивного использования async-функций стоит воспользоваться Async Iterators. JavaScript-разработчик должен хорошо разбираться в этом.



















