目录

第十一部分:设计模式与架构

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 的架构设计模式:

继续阅读 第十二部分:实战案例