Last active
August 11, 2020 04:31
-
-
Save piglovesyou/a7c49e14feab386a673433362505e34b to your computer and use it in GitHub Desktop.
IndexedDB基礎
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// リクエスト系の基底クラスはIDBRequest | |
/** @type IDBOpenDBRequest */ | |
const dbOpenRequest = indexedDB.open('mydb', 1); | |
/* | |
内部の実行順序(非同期) | |
let req; | |
let error; | |
try { | |
req = indexedDB.open(); | |
if (isUpgradeneeded) { | |
req.onupgradeneeded(); | |
} | |
} catch(e) { | |
error = e; | |
} | |
if (error) { | |
req.onerror(error); | |
} else { | |
req.onsuccess(); | |
} | |
*/ | |
// 初回とバージョンアップ時に実行される | |
dbOpenRequest.onupgradeneeded = e => { | |
const db = e.target.result; | |
const tx = e.target.transaction; | |
tx.mode; // 'versionchange' | |
/** @type IDBObjectStore */ | |
let mystore; | |
try { | |
// objectStore がない場合、作成される | |
// objectStore があった場合 DOMException が投げられる: | |
// DOMException: Failed to execute 'createObjectStore' on 'IDBDatabase': | |
// An object store with the specified name already exists | |
// keyPathでprimary keyに'id'を使うことを宣言 | |
mystore = db.createObjectStore('mystore', { keyPath: 'id' }); // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore#Parameters | |
} catch (e) { | |
// onupgradeneededでの既存のobjectStore取得 | |
// 'readonly'(default) or 'readwrite' | |
mystore = tx.objectStore('mystore'); | |
} | |
// createIndex, deleteIndex はonupgradeneeded でしか実行できない | |
// createIndexのindexがすでにあった場合エラーが投げられる: | |
// Uncaught ConstraintError: Faied to execute 'createIndex' on | |
// 'IDBObjectStore': An index wih the specified name already exists. | |
try { | |
// 第1引数: インデックス名 | |
// 第2引数: ソートしておくカラム名 | |
/** @type IDBIndex */ | |
const mystoreTitleIndex = mystore.createIndex('byTitle', 'title'); | |
} catch(e) {} | |
try { | |
mystore.createIndex('byBody', 'body'); | |
} catch(e) {} | |
try { | |
mystore.createIndex('byStatus', 'status'); | |
} catch(e) {} | |
try { | |
// 複数カラムでソートしたインデックスも作れる | |
// http://stackoverflow.com/a/15625231/804314 | |
mystore.createIndex('by title, body and status', ['title', 'body', 'status']); | |
} catch(e) {} | |
}; | |
// onupgradeneeded が呼ばれても呼ばれなくても、毎回次に実行される | |
dbOpenRequest.onsuccess = e => { | |
const db = e.target.result; | |
e.target.transaction; // null. 自分で開始する | |
// この実行単位で外部(例:別タブ)からの影響がないことが保証される | |
/** @type IDBTransaction */ | |
const transaction = db.transaction(['mystore'], 'readwrite'); | |
/** @type IDBObjectStore */ | |
const mystore = transaction.objectStore('mystore'); | |
// openCursorとopenKeyCursorの速度簡易比較:openKeyCursorは若干速いが用途が限られる | |
// console.time('openCursor 1000 records'); | |
// mystore.openCursor(IDBKeyRange.bound("id_0001", "id_1000")).onsuccess = e => { | |
// const cursor = e.target.result; | |
// if (cursor) { | |
// // cursor.valueでレコードが見れる | |
// cursor.continue(); | |
// } else { | |
// console.timeEnd('openCursor 1000 records'); // 74ms, 76ms, 98ms | |
// } | |
// }; | |
// console.time('openKeyCursor 1000 records'); | |
// mystore.openKeyCursor(IDBKeyRange.bound("id_0001", "id_1000")).onsuccess = e => { | |
// const cursor = e.target.result; | |
// if (cursor) { | |
// // cursor.key でフィールドの値だけ見れる。レコード全体は見れない | |
// cursor.continue(); | |
// } else { | |
// console.timeEnd('openKeyCursor 1000 records'); // 64ms, 62ms, 60ms | |
// } | |
// }; | |
// ダミーデータの登録 | |
for (let i = 0; i < 9999; i++) { | |
mystore.put({ | |
id: 'id_' + padLeft(i, 4, '0'), | |
title: 'title_' + padLeft(random(0, 99), 4, '0'), | |
body: 'body_' + padLeft(random(0, 99), 4, '0'), | |
status: random(1, 5), | |
}); | |
} | |
// id = 'id_0555' でレコードの配列を取得する | |
mystore.getAll('id_0555').onsuccess = e => { | |
/** @type Array<Object> */ | |
const records = e.target.result; | |
}; | |
// クエリを使ってレコードの配列を取得する: | |
// クエリは範囲(range)を指定するやり方(LIKE '%xx%'とかは無い) | |
// Syntax: IDBKeyRange.bound(start, end, [startを除外], [endを除外]) | |
// Syntax: 他にも .upperBound, .lowerBoundがある | |
/** @type IDBKeyRange */ | |
const keyRangeValue = IDBKeyRange.bound("id_0500", "id_0600"); | |
// Syntax: objectStore.getAll([query, maxCount]); | |
mystore.getAll(keyRangeValue, 200).onsuccess = e => { | |
/** @type Array<Object> */ | |
const records = e.target.result; | |
}; | |
// カーソルを用いて見つけたいレコードを非同期にイテレートさせる: | |
// Syntax: ObjectStore.openCursor(optionalKeyRange, optionalDirection) | |
// optionalDirection: 'next' 'nextunique' 'prev' 'prevunique' | |
// カーソルが何か見つけるたびに何度も実行される | |
mystore.openCursor(keyRangeValue).onsuccess = e => { | |
/** @type IDBCursor */ | |
const cursor = e.target.result; | |
if (cursor) { | |
// レコードを見れる cursor.value | |
// console.log(cursor.value.title, cursor.value.body); | |
cursor.continue(); // 1つカーソルを進める | |
// cursor.advance(3); 3つカーソルを進める | |
// cursor.delete(); カーソルに対応するレコードを消す。位置は変わらない | |
// cursor.update(); カーソルに対応するレコードを更新。位置は変わらない | |
} else { | |
// すべてをiteraterし終わるとここに来る | |
} | |
} | |
// primary key以外のフィールドで検索するには作っておいたindexを使う | |
// IDBIndexのAPIはIDBObjectStoreと大体同じで書き込みはない | |
// 書き込む | |
/** @type IDBIndex */ | |
const mystoreTitleIndex = mystore.index('byTitle'); | |
const titleKeyRange = IDBKeyRange.bound('title_0150', 'title_0177'); | |
mystoreTitleIndex.openCursor(titleKeyRange).onsuccess = e => { | |
const cursor = e.target.result; | |
if (cursor) { | |
// cursor.value でレコードが見れる | |
cursor.continue(); | |
} | |
}; | |
// 複数カラムをソートして作ったインデックスを検索するには | |
// IDBKeyRangeを複数カラムで作る | |
const mystoreCompoundedIndex = mystore.index('by title, body and status'); | |
const compoundedKeyRange = IDBKeyRange.bound(['title_0200', 'body_2000', 3], | |
['title_0400', 'body_5000', 5]); | |
mystoreCompoundedIndex.openCursor(compoundedKeyRange).onsuccess = e => { | |
const cursor = e.target.result; | |
if (cursor) { | |
// cursor.value でレコードが見れる | |
cursor.continue(); | |
} | |
}; | |
}; | |
dbOpenRequest.onerror = e => { | |
console.log('error', e); | |
}; | |
// データベースを消したいときは | |
// const dbDeleteRequest = indexedDB.deleteDatabase('mydb'); | |
function padLeft(s, len, padstr) { | |
if (padstr.length !== 1) throw new Error('yeah'); | |
s = String(s); | |
while (s.length < len) s = padstr + s; | |
return s; | |
} | |
function random(from, to) { | |
return Math.floor(Math.random() * (to - from + 1) + from); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment