ブラウザでは、スクリプト、iframe、画像などの外部リソースの読み込みを追跡できます。
これには2つのイベントがあります
onload
- 読み込み成功,onerror
- エラー発生。
スクリプトの読み込み
サードパーティのスクリプトを読み込んで、そこに存在する関数を呼び出す必要があるとしましょう。
次のように動的に読み込むことができます
let script = document.createElement('script');
script.src = "my.js";
document.head.append(script);
…しかし、そのスクリプト内で宣言されている関数をどのように実行するのでしょうか?スクリプトが読み込まれるまで待って、それから初めて呼び出すことができます。
独自のスクリプトには、ここでJavaScriptモジュールを使用できますが、サードパーティのライブラリでは広く採用されていません。
script.onload
主なヘルパーはload
イベントです。スクリプトが読み込まれて実行された後にトリガーされます。
例えば
let script = document.createElement('script');
// can load any script, from any domain
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
document.head.append(script);
script.onload = function() {
// the script creates a variable "_"
alert( _.VERSION ); // shows library version
};
そのため、onload
ではスクリプト変数を使用したり、関数を実行したりできます。
…そして、読み込みに失敗した場合はどうでしょうか?たとえば、そのようなスクリプトがない(エラー404)か、サーバーがダウンしている(利用不可)場合があります。
script.onerror
スクリプトの読み込み中に発生したエラーは、error
イベントで追跡できます。
たとえば、存在しないスクリプトをリクエストしてみましょう
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);
script.onerror = function() {
alert("Error loading " + this.src); // Error loading 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(`Image loaded, size ${img.width}x${img.height}`);
};
img.onerror = function() {
alert("Error occurred while loading image");
};
ただし、いくつかの注意点があります
- ほとんどのリソースは、ドキュメントに追加されると読み込みを開始します。ただし、
<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://javascriptinfo.dokyumento.jp/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
属性があり、リモートサーバーが特別なヘッダーを提供する必要があります。
クロスオリジンアクセスには3つのレベルがあります
crossorigin
属性なし - アクセス禁止。crossorigin="anonymous"
- サーバーがAccess-Control-Allow-Origin
ヘッダーで*
または当社のオリジンで応答した場合にアクセスが許可されます。ブラウザは認証情報とCookieをリモートサーバーに送信しません。crossorigin="use-credentials"
- サーバーがAccess-Control-Allow-Origin
ヘッダーで当社のオリジンとAccess-Control-Allow-Credentials: true
を送信した場合にアクセスが許可されます。ブラウザは認証情報とCookieをリモートサーバーに送信します。
クロスオリジンアクセスの詳細については、Fetch:クロスオリジンリクエストの章で読むことができます。ネットワークリクエストのfetch
メソッドについて説明していますが、ポリシーはまったく同じです。
「Cookie」のようなものは現在の範囲外ですが、Cookie、document.cookieの章で読むことができます。
私たちの場合、crossorigin属性はありませんでした。そのため、クロスオリジンアクセスは禁止されていました。追加してみましょう。
"anonymous"
(Cookieは送信されず、サーバー側のヘッダーが1つ必要)と"use-credentials"
(Cookieも送信され、サーバー側のヘッダーが2つ必要)から選択できます。
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
イベントの方がシンプルなので、めったに使用されません。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。