はじめに (対象読者・この記事でわかること)

この記事は、Web フロントエンド開発を行うエンジニア、特に外部 JavaScript ライブラリやウィジェット(例: widgets.js)を動的に読み込む機会が多い方を対象としています。
本稿を読むことで、次のことができるようになります。

  • <script> タグで外部ファイルを読み込んだときの 読み込み完了検知 方法を複数把握できる
  • onload / onerrorPromiseasync/await を組み合わせた 安全な実装パターン が身につく
  • 読み込み失敗時のエラーハンドリングや、重複ロード防止 のベストプラクティスを理解できる

本記事は、実務で「外部ウィジェットがいつ利用可能になるか」を正確に制御したいシーンが増えてきたことが背景です。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • HTML と基本的な DOM 操作(document.createElement など)の理解
  • JavaScript の非同期処理(Promiseasync/await)に関する基本的な知識
  • ブラウザのイベントフロー(DOMContentLoadedload)の概念

外部スクリプト読み込み完了検知の概要と背景

Web ページに外部ウィジェット(例: widgets.js)を埋め込むケースは多く、広告、解析、チャットボット、地図表示など多岐にわたります。
しかし、外部スクリプトは ネットワーク遅延やキャッシュ状態 に左右され、読み込みが完了したタイミングを誤ると次のような問題が発生します。

  • ウィジェットの API が未定義のまま呼び出し、Uncaught ReferenceError がスローされる
  • 複数回同一スクリプトをロードしてしまい、グローバル名前空間が汚染される
  • 読み込みエラー(404、CORS エラー等)に対処できず、ユーザー体験が低下する

このため、正確に「読み込み完了」を検知し、かつエラー時にフォールバック できる実装が必須です。
本節では、ブラウザが提供する標準的なイベント(loaderror)と、近年のモダン JavaScript で推奨される Promise ラップ、async/await の組み合わせを中心に解説します。

具体的な手順や実装方法

外部スクリプトを確実に読み込んで完了を検知するには、主に 3 つのパターンがあります。

  1. <script> タグの onload / onerror ハンドラを直接利用
  2. Promise でラップし、async/await で呼び出し
  3. MutationObserver で動的に挿入された <script> を監視(高度なケース)

以下、最も汎用的で保守性の高い パターン 2 を中心に、ステップ別に実装例と注意点を示します。

ステップ1:スクリプト要素を動的に生成し、onloadonerror を設定

Javascript
function loadScript(src) { return new Promise((resolve, reject) => { // 既に同じスクリプトがロード済みかチェック if (document.querySelector(`script[src="${src}"]`)) { resolve(); // 既にロードされているので即解決 return; } const script = document.createElement('script'); script.src = src; script.async = true; // 必要に応じて async / defer を切り替える script.onload = () => { console.log(`✅ ${src} の読み込みが完了しました`); resolve(); }; script.onerror = (e) => { console.error(`❌ ${src} の読み込みに失敗しました`, e); reject(new Error(`Failed to load script ${src}`)); }; document.head.appendChild(script); }); }

ポイント解説

  • document.querySelector で同一 src<script> が存在すれば 二重ロードを防止 し、resolve() で即完了扱いにします。
  • script.async = true により、他のリソースとのロード順序に依存しない非同期ロードが保証されます。
  • onloadonerror の両方を必ず設定し、エラーハンドリング を忘れないことが重要です。

ステップ2:async/await で呼び出し、実装全体をシンプルに保つ

Javascript
async function initWidgets() { try { await loadScript('https://cdn.example.com/widgets.js'); // widgets.js が確実にロードされたので、グローバルオブジェクトや API を使用できる if (window.Widgets) { Widgets.init({ theme: 'dark' }); } else { console.warn('Widgets オブジェクトが見つかりません'); } } catch (err) { // 読み込み失敗時のフォールバック処理 console.error('ウィジェットの初期化に失敗しました', err); // 例: 代替 UI の表示やユーザーへの通知 showFallbackMessage(); } } initWidgets();

ポイント解説

  • await loadScript(...)ロード完了エラー かを自動的に判定し、try/catch で例外処理が統一されます。
  • window.Widgets が期待通りに定義されているかを 二重チェック することで、スクリプト自体はロードされたが内部エラーが起きたケースにも対処できます。

ステップ3:MutationObserver を使って「外部が自動で script タグを埋め込む」ケースに備える

一部のサードパーティウィジェットは、ページロード後に自分自身の <script> タグを挿入します。このようなケースでは、自前で loadScript を呼ぶ前に、スクリプトが実際に DOM に追加されたかを監視する必要があります。

Javascript
function observeScriptInsertion(targetSrc, callback) { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.tagName === 'SCRIPT' && node.src.includes(targetSrc)) { node.addEventListener('load', () => callback(null)); node.addEventListener('error', (e) => callback(e)); } } } }); observer.observe(document.head, { childList: true }); } // 使用例 observeScriptInsertion('widgets.js', (err) => { if (err) { console.error('外部ウィジェットのロードに失敗', err); } else { console.log('外部ウィジェットがロード完了'); // 初期化処理 Widgets.init(); } });

ポイント解説

  • MutationObserver は DOM の変更をリアルタイムで取得でき、スクリプトが 自動挿入 されたタイミングを捕捉します。
  • observer.disconnect() は不要になったら必ず呼び、メモリリークを防止してください。

ハマった点やエラー解決

発生した問題 原因 解決策
onload が呼ばれず、await が永遠に待ち状態になる script.async = false(デフォルト)で他のスクリプトブロックが発生 script.async = true に設定し、非同期ロードに変更
同一 src を二度ロードし、Uncaught TypeError: Cannot read property 'init' of undefined 重複ロードによりウィジェットが再初期化され、内部状態がリセットされた loadScript 内で document.querySelector による重複チェックを実装
CORS エラーで onerror が発火しない スクリプトが別ドメインかつ Access-Control-Allow-Origin が設定されていない サーバ側で正しい CORS ヘッダーを付与、もしくは同一オリジンの CDN を利用
MutationObserver が対象スクリプトを見逃す 監視対象が document.body しか指定していなかった document.head もしくは document.documentElement を対象に childList: true を設定

解決策まとめ

  1. 必ず onloadonerror を設定し、Promise にラップ して async/await で扱う
  2. 二重ロード防止 のために、事前に同一 src が存在しないかチェック
  3. エラーハンドリング(ネットワーク・CORS・スクリプト内部エラー)を try/catch で統一
  4. 自動挿入スクリプト がある場合は MutationObserver で確実に捕捉

以上のステップを踏むことで、外部ウィジェットのロード完了を安全・確実に検知でき、後続のロジックを安定して実行できます。

まとめ

本記事では、外部 JavaScript ファイル widgets.js の読み込み完了検知 方法を、onload/onerror の直接利用から Promise ラップ、async/await、さらには MutationObserver まで網羅的に解説しました。

  • 二重ロード防止エラーハンドリング を組み込んだ loadScript 関数で安全にスクリプトをロード
  • async/await により非同期処理をシンプルに記述し、失敗時はフォールバック処理を実装
  • サードパーティが自動で <script> を挿入するケースは MutationObserver で監視

これらを活用すれば、外部ウィジェットがいつ利用可能になるか正確に把握でき、ユーザー体験の低下やデバッグコストの削減につながります。今後は、モジュール化された CDN スクリプトの動的インポートService Worker と組み合わせたキャッシュ戦略 についても取り上げる予定です。

参考資料