IndexedDB客户端分页查询实现方法
IndexedDB是一种浏览器内置的NoSQL数据库,适合在客户端存储大量结构化数据。以下是实现分页查询的几种方法:
基本分页实现
1. 使用游标(cursor)和偏移量(offset)
function getPaginatedData(dbName, storeName, pageNumber, pageSize) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const data = [];
let advanced = false;
let counter = 0;
// 计算跳过多少条记录
const skip = (pageNumber - 1) * pageSize;
store.openCursor().onsuccess = (cursorEvent) => {
const cursor = cursorEvent.target.result;
if (!cursor) {
resolve(data);
return;
}
// 跳过前N条记录
if (!advanced && skip > 0) {
advanced = true;
cursor.advance(skip);
return;
}
// 收集当前页数据
if (counter < pageSize) {
data.push(cursor.value);
counter++;
cursor.continue();
} else {
resolve(data);
}
};
store.openCursor().onerror = (error) => {
reject(error);
};
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
2. 使用IDBKeyRange和索引
function getPaginatedDataByIndex(dbName, storeName, indexName, pageNumber, pageSize) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const data = [];
let counter = 0;
const skip = (pageNumber - 1) * pageSize;
const upperBound = skip + pageSize;
// 使用键范围获取特定范围内的记录
const keyRange = IDBKeyRange.bound(skip, upperBound, false, true);
index.openCursor(keyRange).onsuccess = (cursorEvent) => {
const cursor = cursorEvent.target.result;
if (cursor) {
data.push(cursor.value);
cursor.continue();
} else {
resolve(data);
}
};
index.openCursor(keyRange).onerror = (error) => {
reject(error);
};
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
优化分页实现
1. 带排序的分页
function getSortedPaginatedData(dbName, storeName, indexName, pageNumber, pageSize, direction = 'next') {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const data = [];
let counter = 0;
const skip = (pageNumber - 1) * pageSize;
const cursorRequest = direction === 'prev'
? index.openCursor(null, 'prev')
: index.openCursor();
cursorRequest.onsuccess = (cursorEvent) => {
const cursor = cursorEvent.target.result;
if (!cursor) {
resolve(data);
return;
}
// 跳过前N条记录
if (counter < skip) {
counter++;
cursor.continue();
return;
}
// 收集当前页数据
if (data.length < pageSize) {
data.push(cursor.value);
cursor.continue();
} else {
resolve(data);
}
};
cursorRequest.onerror = (error) => {
reject(error);
};
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
2. 带过滤的分页
function getFilteredPaginatedData(dbName, storeName, filterFn, pageNumber, pageSize) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName);
request.onsuccess = (event) => {
const db = event.target.result;
const transaction = db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
const data = [];
let filteredCounter = 0;
let pageCounter = 0;
const skip = (pageNumber - 1) * pageSize;
store.openCursor().onsuccess = (cursorEvent) => {
const cursor = cursorEvent.target.result;
if (!cursor) {
resolve(data);
return;
}
// 应用过滤函数
if (filterFn(cursor.value)) {
// 跳过前N条过滤后的记录
if (filteredCounter < skip) {
filteredCounter++;
cursor.continue();
return;
}
// 收集当前页数据
if (pageCounter < pageSize) {
data.push(cursor.value);
pageCounter++;
cursor.continue();
} else {
resolve(data);
}
} else {
cursor.continue();
}
};
store.openCursor().onerror = (error) => {
reject(error);
};
};
request.onerror = (event) => {
reject(event.target.error);
};
});
}
使用建议
- 索引优化:为经常用于排序或过滤的字段创建索引
- 批量处理:对于大数据集,考虑使用IDBKeyRange分批处理
- 内存管理:避免一次性加载过多数据到内存
- 错误处理:始终添加适当的错误处理逻辑
- 事务管理:合理使用事务,避免长时间占用数据库连接
示例调用
// 打开或创建数据库
const dbRequest = indexedDB.open('MyDatabase', 1);
dbRequest.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('customers', { keyPath: 'id' });
store.createIndex('name', 'name', { unique: false });
};
// 使用分页函数
getPaginatedData('MyDatabase', 'customers', 2, 10)
.then(data => {
console.log('Page 2 data:', data);
})
.catch(error => {
console.error('Error:', error);
});
以上方法提供了IndexedDB实现分页查询的几种不同方式,您可以根据具体需求选择最适合的方案。
// 来源:https://www.nzw6.com