对 SSE 的简单封装

import { fetchEventSource } from '@microsoft/fetch-event-source'

class FetchEventSourceController {
  constructor(url, options = {}) {
    this.url = url
    // 为 options 提供默认值,确保请求可以正常发起
    this.options = {
      headers: {
        Accept: 'application/json', // 默认接受 JSON 格式
        'Content-Type': 'application/json', // 默认请求内容类型为 JSON
        ...options.headers // 允许外部传入自定义 headers
      },
      body: options.body || null, // 如果外部没有传入 body,默认为 null
      method: options.method || 'GET', // 默认为 GET 请求
      reconnectInterval: options.reconnectInterval || 2000, // 默认为 2000ms 重连间隔
      maxReconnectAttempts: options.maxReconnectAttempts || 15 // 最大重连次数
      // handleMessage: options.handleMessage || (() => {}), // 默认 handleMessage 为一个空函数
    }

    this.reconnectInterval = this.options.reconnectInterval // 重连时间间隔
    this.maxReconnectAttempts = this.options.maxReconnectAttempts // 最大重连次数
    this.reconnectAttempts = 0 // 记录当前重连尝试次数
    this.isReconnecting = false // 是否主动断开
    this.eventSource = null // 控制器
    this.reconnectTimeout = null // 用于存储定时器,防止重复重连

    this.messageQueue = [] // 用于存储待处理的消息队列

    this._messageLock = false // 标志是否有消息正在处理
    this._handleQueueID = null //队列消息派发计时器
    this._heardCheckID = null //心跳包计时器

    this.connect()
  }

  connect() {
    const {
      headers,
      body,
      method
      // handleMessage
    } = this.options

    this.eventSource = fetchEventSource(this.url, {
      headers, // 使用自定义 headers 或默认值
      body, // 使用自定义 body 或默认值
      method, // 使用自定义 method 或默认值
      onopen: response => {
        // console.log('onopen', response); // 也可以调用外部传入的 handleMessage 函数
        this._messageLock = false // 队列上锁
        this.isReconnecting = false // 队列上锁
        this.reconnectAttempts = 0 // 重置重连尝试次数
        this.initOpen() // 启动事件队列机制
      },
      onmessage: event => {
        // 将收到的消息入队
        this.pushQueue(event.data)
      },
      onerror: error => {
        // console.log('onerror', error); // 也可以调用外部传入的 handleMessage 函数
        this.handleReconnect()
      },
      onclose: () => {
        // console.log('onclose'); // 也可以调用外部传入的 handleMessage 函数
        this.handleReconnect()
      }
    })
  }

  initOpen(event, fCallBack) {
    this._handleQueueID && clearInterval(this._handleQueueID)
    this._handleQueueID = setInterval(this.handleQueue, 100)

    fCallBack && fCallBack.call()
  }

  // 事件派发
  async handleQueue() {
    let data = this.getQueue() // 获取第一条消息
    if (this.messageQueue.length) {
      if (this._messageLock) {
        this._messageLock = false //消息队列上锁(排队处理)
        data = this.getQueueHeadMessage() //获取头部第一条消息
        this.handleMessage(data) //事件处理派发
      } else {
        let nowTime = new Date().getTime()
        if (nowTime - data._queueRecvTime.getTime() > 10000) {
          //队列存在超过10s没处理的,强制处理
          this._messageLock = true
        }
      }
    }
  }

  // 消息处理
  handleMessage(_data) {
    switch (_data.dataType) {
      case 2: //订阅消息
        break
      case 3: //版本号广播
        break
      case 4: //顶号
        break
      case 5: //同步退出登陆
        break
      case 6: //行情
        break
    }

    this.dealQueue() //时间处理完毕,继续解锁队列
  }

  // 延时
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  // 加入消息队列
  pushQueue(evtData) {
    evtData._queueRecvTime = new Date()
    this.messageQueue.push(evtData)
  }

  //获取队列头第一条消息
  getQueueHeadMessage() {
    if (this.messageQueue.length > 0) {
      return this.messageQueue.shift()
    } else {
      return null
    }
  }

  //队列消息解锁
  dealQueue() {
    this._messageLock = true
  }

  handleReconnect() {
    // 如果已经达到了最大重连次数,停止重连
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.log('达到最大重连次数,停止重连')
      return
    }

    if (this.isReconnecting) {
      console.log('主动断开,停止重连')
      return
    }

    console.log(`等待 ${this.reconnectInterval / 1000} 秒后重连...`)

    this.reconnectAttempts += 1
    this.reconnectTimeout = setTimeout(() => {
      this.connect()
    }, this.reconnectInterval)
  }

  close() {
    if (this.eventSource) {
      this.eventSource.close()
      console.log('手动关闭连接')
    }

    this.reconnectTimeout && clearTimeout(this.reconnectTimeout) // 清除重连定时器
    this._handleQueueID && clearInterval(this._handleQueueID) // 清除事件分发定时器

    // 停止重连
    this.isReconnecting = true
    this.reconnectAttempts = 0 // 重置重连尝试次数
  }
}

// 使用示例
const customHeaders = {
  Authorization: 'Bearer your-token-here',
  'Content-Type': 'application/json'
}

const requestBody = JSON.stringify({
  param1: 'value1',
  param2: 'value2'
})

// 统一处理回调
const handleMessage = (eventType, event) => {
  switch (eventType) {
    case 'onopen':
      console.log('连接已打开', event)
      break
    case 'onmessage':
      console.log('收到消息:', event.data)
      break
    case 'onerror':
      console.error('连接错误:', event)
      break
    case 'onclose':
      console.log('连接关闭')
      break
    default:
      console.log(`未知事件: ${eventType}`)
  }
}

const eventSource = new FetchEventSourceController('https://your-server.com/stream', {
  headers: customHeaders,
  body: requestBody,
  reconnectInterval: 3000, // 自定义重连间隔为 3 秒
  maxReconnectAttempts: 5 // 最大重连次数为 5
  // handleMessage // 也可以传入统一的事件回调函数
})

// 如果需要关闭连接,可以调用:
setTimeout(() => {
  eventSource.close() // 关闭连接并停止重连
}, 15000) // 15 秒后主动关闭连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值