'indexedDB' in window で判定を行っていけない理由

IndexedDBが使用可能か、'indexedDB' in window だけで判定を行っていけない理由

―― “'indexedDB' in window だけではダメ” の理由と、堅牢判定ロジックについて――

TL;DR

  1. 存在チェックだけでは穴だらけ。プライベートモードや企業ポリシーで “実際には使えない” ケースが多い。
  2. 必ず小さなテスト DB を open() してみる。成功したら close + delete。
  3. 結果は LocalStorage にキャッシュ して 2 回目以降を高速化。
  4. LocalStorage 書き込み自体も try/catch。iOS Safari のプライベートモードはここで落ちる。
  5. タイムアウトを仕込む。onerror も発火しないままフリーズする古い Android ブラウザがある。

この 5 ステップを押さえれば、「ユーザーの環境依存で IndexedDB が突然使えずにアプリが壊れる」トラブルをほぼゼロにできます。

目次

  1. そもそも IndexedDB とは?
  2. 'indexedDB' in window が落とし穴になる理由
  3. “実使用チェック” を行うユーティリティを作る
  4. 結果をキャッシュして高速化する
  5. 実戦投入! jQuery + Promise でメイン処理に組み込む
  6. デバッグ & テスト用 Tips
  7. よくある質問と補足
  8. 全部入りスニペット(コピーして即利用)
  9. おわりに:未来のブラウザ事情と勘所

1. そもそも IndexedDB とは?

IndexedDB はブラウザに組み込まれた スキーマレスな NoSQL データベース です。

Web アプリを オフラインファースト にしたり、チャットログ・地図タイル・画像キャッシュなどを保存したりする際の第一候補です。

2. 'indexedDB' in window が落とし穴になる理由

“存在チェック” は一見正しく見えます。が、実際には 4 つの罠 が潜んでいます。

#再現環境症状
1プライベートモード規制iOS / macOS Safariopen()QuotaExceededError
2実装バグ旧 Samsung Internet / UC Browseropen() 後に onerror も来ずハング
3企業ポリシーChrome + GPOストレージ上限 0B、UnknownError
4sandbox 付き iframe広告ウィジェット等SecurityError

要するに「プロパティがある=安心」とは言えません。“実際に DB を開けるか” をテストしない限り、信頼できないのです。

3. “実使用チェック” を行うユーティリティを作る

3-1. 要件

  1. プロパティが無ければ即 false
  2. open() 成功で close() & deleteDatabase()
  3. try/catch で同期例外も掴む
  4. ハングに備えタイムアウト
  5. Promise<boolean> で返す

3-2. 実装

function isIndexedDBAvailable() {
  return new Promise(resolve => {
    if (!('indexedDB' in window)) return resolve(false);

    let finished = false;
    try {
      const req = indexedDB.open('___test___');
      req.onerror = () => resolve(false);
      req.onsuccess = e => {
        finished = true;
        e.target.result.close();
        indexedDB.deleteDatabase('___test___');
        resolve(true);
      };
      setTimeout(() => !finished && resolve(false), 3000);
    } catch (_) {
      resolve(false);
    }
  });
}

4. 結果をキャッシュして高速化する

open / delete を毎回実行するのは無駄なので LocalStorage キャッシュを利用します。

let useIndexedDB   = false;
let localStorageOK = true;

function checkIndexedDBOnce() {
  if (window.localStorage && localStorage.getItem('useIndexedDB')) {
    useIndexedDB = localStorage.getItem('useIndexedDB') === '1';
    return Promise.resolve();
  }
  return isIndexedDBAvailable().then(ok => {
    useIndexedDB = ok;
    if (window.localStorage) {
      try { localStorage.setItem('useIndexedDB', ok ? '1' : '0'); }
      catch (_) { localStorageOK = false; }
    }
  });
}

5. 実戦投入! jQuery + Promise でメイン処理に組み込む

$(document).ready(() => {
  checkIndexedDBOnce()
    .finally(() => {
      if (useIndexedDB) {
        console.log('[OK] IndexedDB available ✔');
        startAppWithIndexedDB();
      } else {
        console.warn('[WARN] IndexedDB unavailable, fallback...');
        startAppFallback();
      }
    });
});

6. デバッグ & テスト用 Tips

テストしたい現象手順期待される動作
LocalStorage 書き込み失敗iOS Safari プライベートモードlocalStorageOK = false
Quota 制限Chrome DevTools → Application → Clear storage → Storage quota を極小isIndexedDBAvailable() が false
IndexedDB 全削除コンソール:
indexedDB.databases().then(dbs => dbs.forEach(db => indexedDB.deleteDatabase(db.name)));
次回起動時に onupgradeneeded
iframe sandbox<iframe sandbox src="./app.html">SecurityError → false

7. よくある質問と補足

Q1. 毎回チェックは重い?
open + delete は軽量。キャッシュがあればミリ秒で済みます。

Q2. キャッシュは永遠?
ブラウザがストレージを消したら一緒に失われますが、その時は再チェックが走るだけです。

Q3. Service Worker でも使える?
はい。self.indexedDB は同じ API。キャッシュは IDB 側に保存してください。

Q4. 古いブラウザは?
IndexedDB 自体が無いので、そもそも fallback へ誘導すれば OK です。

8. 全部入りスニペット(コピーして即利用)

<script>
/* ===== IndexedDB Safe Check v1.0 ===== */
let useIndexedDB = false;
let localStorageOK = true;

function isIndexedDBAvailable() {
  return new Promise(resolve => {
    if (!('indexedDB' in window)) return resolve(false);
    let settled = false;
    try {
      const req = indexedDB.open('___probe___');
      req.onerror   = () => resolve(false);
      req.onsuccess = e => {
        settled = true;
        e.target.result.close();
        indexedDB.deleteDatabase('___probe___');
        resolve(true);
      };
      setTimeout(() => !settled && resolve(false), 3000);
    } catch (_) { resolve(false); }
  });
}

function checkIndexedDBOnce() {
  if (window.localStorage && localStorage.getItem('useIndexedDB')) {
    useIndexedDB = localStorage.getItem('useIndexedDB') === '1';
    return Promise.resolve();
  }
  return isIndexedDBAvailable().then(ok => {
    useIndexedDB = ok;
    if (window.localStorage) {
      try { localStorage.setItem('useIndexedDB', ok ? '1' : '0'); }
      catch (_) { localStorageOK = false; }
    }
  });
}

/* ==== Example ==== */
$(document).ready(() => {
  checkIndexedDBOnce().finally(() => {
    if (useIndexedDB) startAppWithIndexedDB();
    else               startAppFallback();
  });
});
</script>

9. おわりに:未来のブラウザ事情と勘所

Storage Foundation API や OPFS など新しい永続化手段も登場していますが、互換性と実績という点で 2025 年現在も IndexedDB は主力です。

“存在すること” と “使えること” を切り分ける―― たったそれだけで、現実世界のバグ報告は劇的に減ります。ぜひあなたのアプリでも試してみてください。

この記事が役立ったらぜひシェアしてください!