Браузер дозволяє нам відстежувати завантаження зовнішніх ресурсів – скриптів, фреймів, зображень тощо.
Для цього передбачено дві події:
onload
– успішне завантаження,onerror
– виявлено помилку.
Завантаження скрипта
Скажімо, нам потрібно завантажити сторонній скрипт і викликати функцію, яка там знаходиться.
Ми можемо завантажити його динамічно, наприклад:
let script = document.createElement('script');
script.src = "my.js";
document.head.append(script);
…Але як запустити функцію, оголошену всередині цього скрипта? Нам потрібно почекати, поки скрипт завантажиться, і тільки тоді ми зможемо її викликати.
Для наших власних скриптів ми могли б використовувати модулі JavaScript, але вони не набули широкого поширення у сторонніх бібліотеках.
script.onload
Основним помічником є подія load
. Вона запускається після завантаження та виконання скрипта.
Наприклад:
let script = document.createElement('script');
// можемо завантажувати будь-який скрипт з будь-якого домену
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);
script.onload = function() {
// скрипт створює змінну "_"
alert( _.VERSION ); // показує версію бібліотеки
};
Тож у onload
ми можемо використовувати змінні скрипта, виконувати функції тощо.
…А якщо не вдалося завантажити? Наприклад, такого сценарію немає (помилка 404) або сервер не працює (недоступний).
script.onerror
Помилки, які виникають під час завантаження скрипта, можна відстежити за допомогою події error
.
Наприклад, давайте запросимо скрипт, якого не існує:
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // немає такого скрипта
document.head.append(script);
script.onerror = function() {
alert("Помилка завантаження " + this.src); // Помилка завантаження https://example.com/404.js
};
Зверніть увагу, що ми не можемо отримати відомості про помилку HTTP тут. Ми не знаємо, чи була це помилка 404 чи 500 чи щось інше. Просто не вдалося завантажити.
Події onload
/onerror
відстежують лише саме завантаження.
Помилки, які можуть виникнути під час обробки та виконання скрипта, виходять за рамки цих подій. Тобто: якщо скрипт завантажується успішно, то запускається onload
, навіть якщо в ньому є помилки програмування. Щоб відстежувати помилки скрипта, можна використовувати глобальний обробник window.onerror
.
Інші ресурси
Події load
та error
також працюють для інших ресурсів, в основному для будь-якого ресурсу, який має зовнішній src
.
Наприклад:
let img = document.createElement('img');
img.src = "https://js.cx/clipart/train.gif"; // (*)
img.onload = function() {
alert(`Зображення завантажено, розмір ${img.width}x${img.height}`);
};
img.onerror = function() {
alert("Під час завантаження зображення сталася помилка");
};
Хоча є деякі примітки:
- Більшість ресурсів починають завантажуватися, коли вони додаються до документа. Але
<img>
є винятком. Він починає завантажуватися, коли отримує src(*)
. - Для
<iframe>
подіяiframe.onload
запускається після завершення завантаження iframe, як для успішного завантаження, так і в разі помилки.
Це обумовлено історичними причинами.
Політика кросдоменних запитів
Існує правило: скрипти з одного сайту не можуть отримати доступ до вмісту іншого сайту. Отже, напр. скрипт на https://facebook.com
не може прочитати поштову скриньку користувача на https://gmail.com
.
Або, якщо бути більш точним, одне джерело (триплет домену/порту/протоколу) не може отримати доступ до вмісту з іншого. Тому навіть якщо у нас є субдомен або просто інший порт, це різні джерела без доступу один до одного.
Це правило також впливає на ресурси з інших доменів.
Якщо ми використовуємо скрипт з іншого домену, і в ньому є помилка, ми не можемо отримати відомості про неї.
Наприклад, візьмемо скрипт error.js
, який складається з одного (неправильного) виклику функції:
// 📁 error.js
noSuchFunction();
Тепер завантажте його з того самого сайту, де він розташований:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="/article/onload-onerror/crossorigin/error.js"></script>
Ми можемо побачити хороший звіт про помилки, наприклад:
Uncaught ReferenceError: noSuchFunction is not defined
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
Тепер давайте завантажимо той самий скрипт з іншого домену:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
Звіт відрізняється, наприклад:
Script error.
, 0:0
Деталі можуть відрізнятися залежно від браузера, але ідея одна: будь-яка інформація про внутрішні елементи скрипта, включаючи трасування стеку помилок, прихована. Саме тому, що він з іншого домену.
Навіщо нам потрібні відомості про помилку?
Існує багато сервісів (і ми можемо створити власні), які прослуховують глобальні помилки за допомогою window.onerror
, зберігають помилки та надають інтерфейс для доступу та аналізу до них. Це чудово, оскільки ми бачимо реальні помилки, спричинені нашими користувачами. Але якщо скрипт походить з іншого джерела, то в ньому не так багато інформації про помилки, як ми щойно бачили.
Подібна політика кросдоменних запитів (CORS) застосовується і для інших типів ресурсів.
Щоб дозволити доступ із різних джерел, тег <script>
повинен мати атрибут crossorigin
, а також віддалений сервер повинен надавати спеціальні заголовки.
Існує три рівні кросдоменного доступу:
- Немає атрибута
crossorigin
– доступ заборонено. crossorigin="anonymous"
– доступ дозволений, якщо сервер відповідає із заголовкомAccess-Control-Allow-Origin
з*
або нашим джерелом. Браузер не надсилає інформацію про авторизацію та файли cookie на віддалений сервер.crossorigin="use-credentials"
– доступ дозволений, якщо сервер надсилає назад заголовокAccess-Control-Allow-Origin
з нашим походженням таAccess-Control-Allow-Credentials: true
. Браузер надсилає інформацію про авторизацію та файли cookie на віддалений сервер.
Ви можете прочитати більше про доступ із різних джерел у розділі Fetch: Запити між різними джерелами. Він описує метод fetch
для мережевих запитів, але політика точно така ж.
Таке поняття, як “cookies” не входить до нашої поточної теми, але ви можете прочитати про них у розділі Файли cookies, document.cookie.
У нашому випадку ми не мали атрибута crossorigin. Таким чином, кросдоменний доступ був заборонений. Додаймо його.
Ми можемо вибирати між "anonymous"
(файли cookie не надсилаються, потрібен один заголовок на стороні сервера) та "use-credentials"
(надсилає файли cookie, потрібні два заголовки на стороні сервера).
Якщо нам не важливі файли cookie, то нам підійде "anonymous"
:
<script>
window.onerror = function(message, url, line, col, errorObj) {
alert(`${message}\n${url}, ${line}:${col}`);
};
</script>
<script crossorigin="anonymous" src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
Тепер, якщо припустити, що сервер надає заголовок Access-Control-Allow-Origin
, все в порядку. У нас є повний звіт про помилки.
Підсумки
Зображення <img>
, зовнішні стилі, скрипти та інші ресурси забезпечують події load
та error
для відстеження їх завантаження:
load
спрацьовує при успішному завантаженні,error
спрацьовує при невдалому завантаженні.
Єдиним винятком є <iframe>
: з історичних причин він завжди запускає load
для будь-якого завершення завантаження, навіть якщо сторінка не знайдена.
Подія readystatechange
також працює для ресурсів, але використовується рідко, оскільки події load/error
простіші.