【JS応用】リソース読み込み: onload と onerror

リソース読み込みにおける onload と onerror の重要性と実装戦略

Webアプリケーションのパフォーマンスと堅牢性を語る上で、外部リソースの読み込み管理は避けて通れないトピックです。現代のフロントエンド開発では、画像、スクリプト、スタイルシート、フォントなど、多種多様なリソースを動的に読み込む機会が増えています。このとき、単に要素をDOMに追加するだけでなく、読み込みの成功(onload)と失敗(onerror)を適切にハンドリングすることは、ユーザー体験(UX)を向上させ、アプリケーションのクラッシュを防ぐための生命線となります。本稿では、これらのイベントの仕組みから、モダンな実装パターン、そして実務におけるベストプラクティスまでを深く掘り下げます。

onload と onerror の基本メカニズム

HTMLの各要素(img, script, link, iframeなど)は、リソースの読み込み状態に応じて特定のイベントを発火します。`onload`イベントは、リソースが完全に読み込まれ、利用可能な状態になったタイミングでトリガーされます。一方、`onerror`イベントは、ネットワークエラー、404 Not Found、あるいはContent Security Policy(CSP)によるブロックなど、何らかの理由でリソースの取得に失敗した際に呼び出されます。

注意すべき点は、これらのイベントが「イベント伝播(バブリング)」のルールに従うかどうかです。例えば、`img`要素の`load`や`error`イベントはバブリングしません。そのため、親要素でイベントをキャッチしようとしても反応しないケースがほとんどです。これを理解していないと、イベントリスナーの設計で大きな落とし穴にはまることになります。また、スクリプトの動的読み込みにおいては、`script`タグがDOMに挿入された直後にリソースのフェッチが開始されるため、挿入前にイベントリスナーを登録しておくことが必須条件となります。

動的リソース読み込みの実装パターン

実務では、単なるHTMLタグの配置だけでなく、JavaScriptを用いた動的なリソース生成が頻繁に行われます。特に、遅延読み込み(Lazy Loading)や、条件付きで外部スクリプトを読み込むケースでは、Promiseベースのラッパーを作成するのが一般的です。これにより、非同期処理のフローを制御しやすくなります。

以下のサンプルコードは、外部スクリプトを動的に読み込み、成功と失敗をPromiseで管理する汎用的な実装例です。


/**
 * 外部スクリプトを動的に読み込むPromiseベースの関数
 * @param {string} src - スクリプトのソースURL
 * @returns {Promise<HTMLScriptElement>}
 */
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.async = true;

    script.onload = () => {
      console.log(`Successfully loaded: ${src}`);
      resolve(script);
    };

    script.onerror = (error) => {
      console.error(`Failed to load: ${src}`, error);
      reject(new Error(`Script load error for ${src}`));
    };

    document.head.appendChild(script);
  });
}

// 使用例
loadScript('https://example.com/analytics.js')
  .then(() => {
    // スクリプト読み込み後の初期化処理
    window.initAnalytics();
  })
  .catch((err) => {
    // 読み込み失敗時のフォールバック処理
    console.warn('Analytics library failed to load, disabling tracking.');
  });

onerror を活用したフォールバック戦略

`onerror`の真価は、読み込み失敗時の「リカバリー」にあります。例えば、画像が壊れている場合に代替画像(プレースホルダー)を表示する、あるいは外部CDNのライブラリがダウンしている場合にローカルのフォールバックを読み込むといった処理が考えられます。

画像読み込み失敗時のフォールバック実装は、実務において非常に頻繁に求められる要件です。


const img = document.querySelector('img.dynamic-image');

img.onerror = () => {
  // 元のソースが失敗した場合、代替画像に切り替える
  // 無限ループを防ぐため、一度だけ実行するように設定
  if (img.src !== 'path/to/fallback.png') {
    img.src = 'path/to/fallback.png';
  }
};

ここで注意したいのは、`onerror`ハンドラの中で再度エラーが発生すると、無限ループに陥るリスクがあることです。上記の例のように、ソースURLを確認して切り替えるか、`onerror`イベントリスナーを一度実行したら削除する(`once: true`オプションを使用する)のが安全な実装です。

実務における高度な技術アドバイス

実務の現場では、単にイベントを拾うだけでなく、パフォーマンスと監視の観点から以下の3点を意識してください。

1. リソースの生存監視(Monitoring)
`onerror`イベントが発生した際に、その詳細情報をSentryなどのエラー監視ツールへ送信することは極めて重要です。特に、特定の地域や特定のISPで特定のCDNからリソースが取得できていないという事象は、クライアントサイドのログがなければ検知できません。「どのリソースが、なぜ失敗したのか」をコンテキスト情報(URL、ブラウザ種別、発生時刻)と共に記録しましょう。

2. セキュリティとCSPの考慮
`onerror`が発火する原因の一つに、Content Security Policy(CSP)によるブロックがあります。外部ドメインからのスクリプト読み込みが禁止されている環境では、当然ながら`onerror`が呼ばれます。このとき、ブラウザのコンソールには明確なエラーが表示されますが、アプリケーション側でもこれをハンドリングし、セキュリティポリシーに違反している旨を適切にユーザーへ通知、あるいはサイレントに機能を停止させる判断が必要です。

3. レースコンディションの回避
動的にリソースを読み込む際、複数のスクリプトが依存関係を持つケースでは注意が必要です。例えば、ライブラリAの後にプラグインBを読み込む必要がある場合、`onload`を入れ子にするのではなく、`async/await`とPromise.allを組み合わせることで、可読性の高い非同期制御を実現してください。

パフォーマンスとUXの両立

`onload`のタイミングは、ユーザーがインタラクションを開始できる状態を示す重要な指標です。しかし、すべてのリソースが`onload`されるのを待っていると、メインスレッドがブロックされ、LCP(Largest Contentful Paint)などの指標が悪化します。

重要なリソースは優先的に読み込みつつ、重要度の低いリソースは`onload`を待たずに非同期で処理する、あるいは`requestIdleCallback`を利用してブラウザのアイドル時間に読み込むといった最適化が求められます。また、スクリプトの`defer`属性や`async`属性を適切に使い分けることで、`onload`を待たずとも実行効率を最大化できます。

まとめ

`onload`と`onerror`は、Web開発における最も基本的なイベントでありながら、その挙動を深く理解し、堅牢なエラーハンドリングを構築することは、プロフェッショナルなエンジニアとしての腕の見せ所です。

– `onload`はリソースの準備完了を検知し、初期化のトリガーとして利用する。
– `onerror`は単なるエラー通知ではなく、フォールバックやリカバリーの起点とする。
– 動的読み込みではPromiseを活用し、非同期フローを確実に制御する。
– 失敗時は必ずログを収集し、監視環境に統合して再発防止に努める。

これらの原則を守ることで、予期せぬネットワーク障害やリソースの不整合に対しても、アプリケーションを安定して動作させることが可能になります。常に「失敗すること」を前提とした防御的なプログラミングを心がけ、最高品質のユーザー体験を提供し続けてください。

コメント

タイトルとURLをコピーしました