SSE流式传输封装

156 阅读1分钟

背景是AI对话中流式获取接口返回的数据,前端动态渲染返回的markdown 基础写法

class SSEClient {
  constructor(url, options = {}) {
    this.url = url;
    this.options = options;
    this.eventSource = null;
    this.listeners = new Map();
  }

  connect() {
    this.eventSource = new EventSource(this.url, this.options);
    
    this.eventSource.onopen = (event) => {
      console.log('SSE 连接已建立');
      this.emit('open', event);
    };

    this.eventSource.onmessage = (event) => {
      const data = this.parseData(event.data);
      this.emit('message', data);
    };

    this.eventSource.onerror = (event) => {
      console.error('SSE 连接错误:', event);
      this.emit('error', event);
    };

    return this;
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
    return this;
  }

  emit(event, data) {
    const callbacks = this.listeners.get(event) || [];
    callbacks.forEach(callback => callback(data));
  }

  parseData(data) {
    try {
      return JSON.parse(data);
    } catch {
      return { content: data };
    }
  }

  close() {
    if (this.eventSource) {
      this.eventSource.close();
      this.eventSource = null;
    }
  }
}

// 使用示例
const sseClient = new SSEClient('/api/events')
  .on('open', () => console.log('连接成功'))
  .on('message', (data) => {
    if (data.type === 'chat') {
      updateChatMessage(data.content);
    }
  })
  .on('error', (error) => console.error('连接错误:', error))
  .connect();

自动重连

class ReconnectingSSE {
  constructor(url, options = {}) {
    this.url = url;
    this.options = {
      maxRetries: 5,
      retryDelay: 1000,
      ...options
    };
    this.retryCount = 0;
    this.eventSource = null;
    this.listeners = new Map();
  }

  connect() {
    try {
      this.eventSource = new EventSource(this.url);
      
      this.eventSource.onopen = () => {
        console.log('SSE 重连成功');
        this.retryCount = 0;
        this.emit('open');
      };

      this.eventSource.onmessage = (event) => {
        this.emit('message', JSON.parse(event.data));
      };

      this.eventSource.onerror = () => {
        this.eventSource.close();
        this.reconnect();
      };
    } catch (error) {
      this.reconnect();
    }
  }

  reconnect() {
    if (this.retryCount >= this.options.maxRetries) {
      this.emit('maxRetriesReached');
      return;
    }

    this.retryCount++;
    const delay = this.options.retryDelay * Math.pow(2, this.retryCount - 1);
    
    console.log(`${delay}ms 后重连 (${this.retryCount}/${this.options.maxRetries})`);
    
    setTimeout(() => {
      this.connect();
    }, delay);
  }

  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
    return this;
  }

  emit(event, data) {
    const callbacks = this.listeners.get(event) || [];
    callbacks.forEach(callback => callback(data));
  }
}