从零打造专业级前端 SDK (三):健壮性与离线存储

20 阅读3分钟

前言:在上一篇中,我们实现了多种网络发送策略,让 SDK 能够把数据发出去。但真实的网络环境是不可靠的——弱网、断网、高频触发都是常态。今天,我们要给 SDK 穿上一层"防弹衣",打造一个永不丢单的健壮系统。

1. 痛点:脆弱的直接发送

如果我们只是简单地调用 fetch,会遇到两个致命问题:

  1. 流量风暴:用户快速滑动列表,瞬间产生几十个请求,服务器压力山大。
  2. 数据丢失:用户在地下停车场点击"支付",因无网络导致请求失败,关键转化数据丢失。

为了解决这些问题,我们需要引入两个核心机制:消息队列 (Queue)离线存储 (Store)

2. 消息队列:削峰填谷

我们不能来一个发一个,而是要"攒一攒"再发。这叫批处理 (Batching)

2.1 智能调度器 (Scheduler)

我们创建一个 Scheduler 类来管理队列。它支持两种优先级:

  • immediate (高) :支付、关键点击 -> 立即发送。
  • batch (低) :曝光、滚动 -> 攒够 10 条或 3 秒后发送。
// src/core/Scheduler.ts
export class Scheduler {
  private queue: TrackerEvent[] = [];
  private timer: number | null = null;

  addEvent(event: TrackerEvent, priority: 'immediate' | 'batch' = 'batch') {
    this.queue.push(event);

    // 1. 高优先级:立即发送 (队列里所有数据)
    if (priority === 'immediate') {
      this.flush();
      return;
    }

    // 2. 低优先级:启动定时器 (3秒兜底)
    if (!this.timer) {
      this.timer = window.setTimeout(() => this.flush(), 3000);
    }

    // 3. 队列满了:立即发送 (10条阈值)
    if (this.queue.length >= 10) {
      this.flush();
    }
  }
}

3. 离线存储:IndexedDB 兜底

flush() 发送失败时,数据不能丢,必须存起来。

3.1 为什么选 IndexedDB?

方案容量性能结构化结论
localStorage5MB同步(卡顿)仅字符串❌ 仅适合玩具
IndexedDB>250MB异步支持对象✅ 生产级首选

3.2 仓库实现 (Store)

我们封装一个轻量级的 Store 类,屏蔽 IndexedDB 的复杂 API。

// src/core/Store.ts
export class Store {
  // 发送失败时调用:存入数据库
  async save(events: TrackerEvent[]) {
    const db = await this.getDB();
    const tx = db.transaction('events', 'readwrite');
    events.forEach(e => tx.store.add(e));
  }

  // 网络恢复时调用:取出所有数据
  async getAll() {
    // ...读取逻辑
  }
}

4. 闭环:自动重试机制

最精彩的部分来了。我们在 Scheduler 中监听浏览器的 online 事件,实现全自动恢复

// src/core/Scheduler.ts
constructor() {
  // 监听网络恢复
  window.addEventListener('online', () => {
    console.log('🌐 网络已恢复,正在重发积压数据...');
    this.retryFailedEvents();
  });
}

async retryFailedEvents() {
  // 1. 从 Store 取出所有积压数据
  const events = await this.store.getAll();
  if (events.length === 0) return;

  try {
    // 2. 尝试重发
    await this.sender.send(events);
    // 3. 发送成功,清空 Store
    await this.store.clear();
    console.log('✅ 重发成功!');
  } catch (e) {
    // 4. 依然失败?没事,数据还在 Store 里,下次再试
    console.error('❌ 重发失败,等待下一次机会');
  }
}

5. 总结

通过本篇的改造,我们的 SDK 进化了:

  • 性能:请求数减少 90%(批处理)。
  • 可靠:弱网/断网环境下数据不丢失(离线存储)。

现在,我们的 SDK 已经具备了商业级产品的核心素质。

下一篇,我们将探讨如何让 SDK 走出浏览器,适配 Node.js小程序 等多种运行时环境,实现真正的跨平台架构


本文代码已开源,欢迎 Star