Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
IndexedDB基礎
// リクエスト系の基底クラスは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