IndexedDBが使用可能か、'indexedDB' in window だけで判定を行っていけない理由
―― “'indexedDB' in window
だけではダメ” の理由と、堅牢判定ロジックについて――
TL;DR
- 存在チェックだけでは穴だらけ。プライベートモードや企業ポリシーで “実際には使えない” ケースが多い。
- 必ず小さなテスト DB を open() してみる。成功したら close + delete。
- 結果は LocalStorage にキャッシュ して 2 回目以降を高速化。
- LocalStorage 書き込み自体も try/catch。iOS Safari のプライベートモードはここで落ちる。
- タイムアウトを仕込む。onerror も発火しないままフリーズする古い Android ブラウザがある。
この 5 ステップを押さえれば、「ユーザーの環境依存で IndexedDB が突然使えずにアプリが壊れる」トラブルをほぼゼロにできます。
目次
- そもそも IndexedDB とは?
'indexedDB' in window
が落とし穴になる理由- “実使用チェック” を行うユーティリティを作る
- 結果をキャッシュして高速化する
- 実戦投入! jQuery + Promise でメイン処理に組み込む
- デバッグ & テスト用 Tips
- よくある質問と補足
- 全部入りスニペット(コピーして即利用)
- おわりに:未来のブラウザ事情と勘所
1. そもそも IndexedDB とは?
IndexedDB はブラウザに組み込まれた スキーマレスな NoSQL データベース です。
- 容量が大きい – 数十 MB〜数 GB(ブラウザ依存)
- トランザクション – ACID を一応保証
- 非同期 API – ブロッキングせず UI がカクつかない
- キーインデックス – 検索も速い
Web アプリを オフラインファースト にしたり、チャットログ・地図タイル・画像キャッシュなどを保存したりする際の第一候補です。
2. 'indexedDB' in window
が落とし穴になる理由
“存在チェック” は一見正しく見えます。が、実際には 4 つの罠 が潜んでいます。
# | 罠 | 再現環境 | 症状 |
---|---|---|---|
1 | プライベートモード規制 | iOS / macOS Safari | open() が QuotaExceededError |
2 | 実装バグ | 旧 Samsung Internet / UC Browser | open() 後に onerror も来ずハング |
3 | 企業ポリシー | Chrome + GPO | ストレージ上限 0B、UnknownError |
4 | sandbox 付き iframe | 広告ウィジェット等 | SecurityError |
要するに「プロパティがある=安心」とは言えません。“実際に DB を開けるか” をテストしない限り、信頼できないのです。
3. “実使用チェック” を行うユーティリティを作る
3-1. 要件
- プロパティが無ければ即
false
open()
成功でclose()
&deleteDatabase()
try/catch
で同期例外も掴む- ハングに備えタイムアウト
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 は主力です。
- 判定ロジックはシンプルかつ堅牢に
- 結果をキャッシュして無駄なストレスを与えない
- fallback を必ず用意 – ユーザー環境は予測不能
“存在すること” と “使えること” を切り分ける―― たったそれだけで、現実世界のバグ報告は劇的に減ります。ぜひあなたのアプリでも試してみてください。
この記事が役立ったらぜひシェアしてください!