Iterating with Cursors and Ranges
What is a cursor?
Section titled “What is a cursor?”Reading a single record by key with objectStore.get(key) is fast, but sometimes you need to walk through many records — filtering, aggregating, or transforming as you go. That is what cursors are for.
A cursor points at one record at a time. You open it on an object store (or index), process the current record, then call cursor.continue() to advance to the next one. When there are no more records the cursor resolves to null, signalling that iteration is complete.
const request = objectStore.openCursor();
request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { console.log(cursor.key, cursor.value); cursor.continue(); // advance to next record } else { console.log('Done — no more records'); }};Opening a cursor
Section titled “Opening a cursor”objectStore.openCursor(query, direction) — both arguments are optional.
query— anIDBKeyRangeor a specific key. Omit (or passnull) to iterate all records.direction—"next"(default, ascending),"prev"(descending),"nextunique", or"prevunique".
// All records, ascending order (default)objectStore.openCursor();
// All records, descending orderobjectStore.openCursor(null, 'prev');
// Only records with keys 2–3 (inclusive on both ends)objectStore.openCursor(IDBKeyRange.bound(2, 3));IDBKeyRange — filtering by key
Section titled “IDBKeyRange — filtering by key”IDBKeyRange lets you describe a subset of keys without scanning every record. The four factory methods are:
| Method | What it matches |
|---|---|
IDBKeyRange.only(value) | Exactly one key |
IDBKeyRange.lowerBound(lower, open?) | Keys ≥ lower (pass true to exclude lower itself) |
IDBKeyRange.upperBound(upper, open?) | Keys ≤ upper (pass true to exclude upper itself) |
IDBKeyRange.bound(lower, upper, lowerOpen?, upperOpen?) | Keys between lower and upper, inclusive by default |
IDBKeyRange.only(42) // key === 42IDBKeyRange.lowerBound(10) // key >= 10IDBKeyRange.lowerBound(10, true) // key > 10IDBKeyRange.upperBound(50) // key <= 50IDBKeyRange.bound(10, 50) // 10 <= key <= 50IDBKeyRange.bound(10, 50, true, true) // 10 < key < 50Wrapping cursor iteration in a Promise
Section titled “Wrapping cursor iteration in a Promise”Raw onsuccess callbacks are tedious. Wrapping the pattern in a helper makes it composable with async/await:
function iterateCursor(store, range = null) { return new Promise((resolve, reject) => { const results = []; const req = store.openCursor(range); req.onsuccess = (e) => { const cursor = e.target.result; if (cursor) { results.push(cursor.value); cursor.continue(); } else { resolve(results); } }; req.onerror = () => reject(req.error); });}Runnable: iterate all records, then a range
Section titled “Runnable: iterate all records, then a range”The demo below:
- Opens a database and seeds four player score records.
- Uses a cursor to iterate all records and logs each one.
- Uses
IDBKeyRange.bound(2, 3)to iterate only the records withid2 and 3. - Deletes the database afterwards to leave no trace.