====== 第十一部分:设计模式与架构 ======
===== 11.1 封装层设计 =====
===== 11.1.1 Promise 封装 =====
原生 IndexedDB API 基于事件,代码冗长。Promise 封装让代码更简洁:
class IndexedDBWrapper {
constructor(dbName, version) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
open(upgradeCallback) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
if (upgradeCallback) {
upgradeCallback(event.target.result, event.target.transaction);
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
resolve(this.db);
};
request.onerror = (event) => reject(event.target.error);
request.onblocked = () => reject(new Error('Database blocked'));
});
}
add(storeName, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.add(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
get(storeName, key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
put(storeName, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.put(data);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
delete(storeName, key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
getAll(storeName, query, count) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.getAll(query, count);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
}
// 使用
const db = new IndexedDBWrapper('MyApp', 1);
await db.open((database, transaction) => {
if (!database.objectStoreNames.contains('users')) {
const store = database.createObjectStore('users', { keyPath: 'id' });
store.createIndex('emailIndex', 'email', { unique: true });
}
});
await db.add('users', { id: 1, name: '张三', email: 'zs@example.com' });
const user = await db.get('users', 1);
console.log(user);
===== 11.2 Repository 模式 =====
class UserRepository {
constructor(dbWrapper) {
this.db = dbWrapper;
this.storeName = 'users';
}
async create(user) {
user.createdAt = new Date().toISOString();
return this.db.add(this.storeName, user);
}
async findById(id) {
return this.db.get(this.storeName, id);
}
async findByEmail(email) {
return new Promise((resolve, reject) => {
const transaction = this.db.db.transaction([this.storeName], 'readonly');
const store = transaction.objectStore(this.storeName);
const index = store.index('emailIndex');
const request = index.get(email);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async update(id, updates) {
const user = await this.findById(id);
if (!user) throw new Error('User not found');
Object.assign(user, updates, { updatedAt: new Date().toISOString() });
return this.db.put(this.storeName, user);
}
async delete(id) {
return this.db.delete(this.storeName, id);
}
async findAll(options = {}) {
const { page = 1, pageSize = 20, sortBy = 'createdAt' } = options;
const all = await this.db.getAll(this.storeName);
// 排序
all.sort((a, b) => {
if (a[sortBy] < b[sortBy]) return -1;
if (a[sortBy] > b[sortBy]) return 1;
return 0;
});
// 分页
const start = (page - 1) * pageSize;
return all.slice(start, start + pageSize);
}
}
// 使用
const userRepo = new UserRepository(db);
const user = await userRepo.create({ name: '李四', email: 'ls@example.com' });
const found = await userRepo.findByEmail('ls@example.com');
===== 11.3 事件驱动架构 =====
class IndexedDBEventEmitter {
constructor() {
this.listeners = new Map();
}
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event).push(callback);
}
emit(event, data) {
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(cb => cb(data));
}
}
}
class ObservableDB extends IndexedDBWrapper {
constructor(dbName, version) {
super(dbName, version);
this.events = new IndexedDBEventEmitter();
}
async add(storeName, data) {
const result = await super.add(storeName, data);
this.events.emit('add', { storeName, data, key: result });
this.events.emit(`${storeName}:add`, { data, key: result });
return result;
}
async put(storeName, data) {
const result = await super.put(storeName, data);
this.events.emit('put', { storeName, data, key: result });
this.events.emit(`${storeName}:put`, { data, key: result });
return result;
}
async delete(storeName, key) {
await super.delete(storeName, key);
this.events.emit('delete', { storeName, key });
this.events.emit(`${storeName}:delete`, { key });
return key;
}
subscribe(storeName, event, callback) {
this.events.on(`${storeName}:${event}`, callback);
}
}
// 使用
const db = new ObservableDB('MyApp', 1);
await db.open(/* ... */);
// 监听用户数据变化
db.subscribe('users', 'add', (event) => {
console.log('新用户添加:', event.data);
});
db.subscribe('users', 'put', (event) => {
console.log('用户更新:', event.data);
});
===== 11.4 数据同步模式 =====
class SyncManager {
constructor(db, apiClient) {
this.db = db;
this.api = apiClient;
this.syncQueue = [];
}
// 本地修改后排队同步
async localUpdate(storeName, data) {
await this.db.put(storeName, { ...data, _syncStatus: 'pending' });
this.syncQueue.push({ action: 'update', storeName, data });
}
async localDelete(storeName, key) {
await this.db.put(storeName, { id: key, _syncStatus: 'pending_delete' });
this.syncQueue.push({ action: 'delete', storeName, key });
}
// 执行同步
async sync() {
for (const item of this.syncQueue) {
try {
if (item.action === 'update') {
await this.api.update(item.storeName, item.data);
} else if (item.action === 'delete') {
await this.api.delete(item.storeName, item.key);
}
// 标记为已同步
await this.markSynced(item);
} catch (error) {
console.error('同步失败:', error);
break; // 停止同步,下次重试
}
}
// 清空已处理的队列
this.syncQueue = this.syncQueue.filter(item => item._syncStatus !== 'synced');
}
async markSynced(item) {
if (item.action === 'delete') {
await this.db.delete(item.storeName, item.key);
} else {
const data = await this.db.get(item.storeName, item.data.id);
delete data._syncStatus;
await this.db.put(item.storeName, data);
}
}
// 从服务器拉取更新
async pullFromServer(lastSyncTime) {
const updates = await this.api.getUpdates(lastSyncTime);
for (const update of updates) {
await this.db.put(update.storeName, update.data);
}
}
}
===== 11.5 本章小结 =====
本章介绍了 IndexedDB 的架构设计模式:
* Promise 封装简化异步操作
* Repository 模式提供数据访问抽象
* 事件驱动架构实现数据变化监听
* 同步管理器实现离线-在线数据同步
继续阅读 [[part12-cases|第十二部分:实战案例]]。