2020年10月5日

リソースの読み込み: onloadとonerror

ブラウザでは、スクリプト、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つのレベルがあります

  1. crossorigin属性なし - アクセス禁止。
  2. crossorigin="anonymous" - サーバーがAccess-Control-Allow-Originヘッダーで*または当社のオリジンで応答した場合にアクセスが許可されます。ブラウザは認証情報とCookieをリモートサーバーに送信しません。
  3. 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イベントの方がシンプルなので、めったに使用されません。

タスク

重要度: 4

通常、画像は作成時に読み込まれます。そのため、<img>をページに追加しても、ユーザーはすぐに画像を見ることができません。ブラウザは最初にそれをロードする必要があります。

画像をすぐに表示するには、次のように「事前に」作成します

let img = document.createElement('img');
img.src = 'my.jpg';

ブラウザは画像の読み込みを開始し、キャッシュに記憶します.後で、同じ画像がドキュメントに表示されると(どのように表示されても)、すぐに表示されます。

配列sourcesからすべての画像を読み込み、準備ができたらcallbackを実行する関数preloadImages(sources, callback)を作成します。

たとえば、これは画像の読み込み後にalertを表示します

function loaded() {
  alert("Images loaded")
}

preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);

エラーが発生した場合でも、関数は画像が「読み込まれた」と見なす必要があります。

言い換えれば、すべての画像が読み込まれたか、エラーが発生したときにcallbackが実行されます。

この関数は、たとえば、スクロール可能な画像がたくさんあるギャラリーを表示する予定で、すべての画像が読み込まれていることを確認したい場合に役立ちます。

ソースドキュメントには、テスト画像へのリンクと、それらが読み込まれているかどうかを確認するためのコードがあります。 300を出力する必要があります。

タスクのサンドボックスを開きます。

アルゴリズム

  1. すべてのソースに対してimgを作成します。
  2. すべての画像にonload/onerrorを追加します。
  3. onloadまたはonerrorがトリガーされたら、カウンターを増やします。
  4. カウンター値がソースカウントと等しい場合-完了です: callback()

サンドボックスでソリューションを開きます。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりにGitHubのissueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには、<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkrjsbincodepen…)を使用してください。