sse 两种调用方式

✅ 方式一:new EventSource()(浏览器原生)

一、适用场景

✅ 简单 SSE

✅ GET 请求

✅ 无需 Header / Token

✅ 日志 / 通知 / 监控


二、SSE 消息类型

ts 复制代码
// types/sse.ts
export interface SSEMessage<T = any> {
  event: string
  data: T
}

三、EventSource Hook(推荐)

composables/useEventSource.ts

ts 复制代码
import { ref, onMounted, onUnmounted } from 'vue'

export function useEventSource<T = any>(
  url: string,
  options?: {
    event?: string
  }
) {
  const data = ref<T[]>([])
  const connected = ref(false)
  const error = ref<Event | null>(null)

  let es: EventSource | null = null

  const connect = () => {
    es = new EventSource(url)

    es.onopen = () => {
      connected.value = true
    }

    es.onerror = (err) => {
      error.value = err
      connected.value = false
      es?.close()
    }

    if (options?.event) {
      es.addEventListener(options.event, (e: MessageEvent<string>) => {
        try {
          data.value.push(JSON.parse(e.data))
        } catch {
          data.value.push(e.data as any)
        }
      })
    } else {
      es.onmessage = (e: MessageEvent<string>) => {
        try {
          data.value.push(JSON.parse(e.data))
        } catch {
          data.value.push(e.data as any)
        }
      }
    }
  }

  onMounted(connect)
  onUnmounted(() => es?.close())

  return {
    data,
    connected,
    error,
    close: () => es?.close()
  }
}

四、组件中使用

vue 复制代码
<script setup lang="ts">
import { useEventSource } from '@/composables/useEventSource'

const { data, connected } = useEventSource<{ msg: string }>(
  '/api/sse',
  { event: 'message' }
)
</script>

<template>
  <p>状态:{{ connected ? '已连接' : '断开' }}</p>
  <ul>
    <li v-for="(item, i) in data" :key="i">
      {{ item.msg }}
    </li>
  </ul>
</template>

✅ 方式二:Axios + Fetch Stream(企业级)

一、适用场景

✅ 大模型对话

✅ 需要 Token

✅ POST

✅ 生产环境


二、Axios SSE 请求封装

services/sseAxios.ts

ts 复制代码
import axios from 'axios'

export interface SSEMessage<T = any> {
  event: string
  data: T
}

export async function requestSSE<T = any>(
  url: string,
  onMessage: (msg: SSEMessage<T>) => void,
  options?: {
    method?: 'GET' | 'POST'
    data?: any
    headers?: Record<string, string>
  }
) {
  const response = await axios({
    url,
    method: options?.method ?? 'GET',
    data: options?.data,
    headers: {
      Accept: 'text/event-stream',
      'Cache-Control': 'no-cache',
      ...options?.headers
    },
    responseType: 'stream',
    adapter: 'fetch'
  })

  const reader = response.data.getReader()
  const decoder = new TextDecoder('utf-8')
  let buffer = ''

  while (true) {
    const { done, value } = await reader.read()
    if (done) break

    buffer += decoder.decode(value, { stream: true })

    const parts = buffer.split('\n\n')
    buffer = parts.pop() || ''

    for (const part of parts) {
      const lines = part.split('\n')
      let event = 'message'
      let data = ''

      for (const line of lines) {
        if (line.startsWith('event:'))
          event = line.replace('event:', '').trim()
        if (line.startsWith('data:'))
          data = line.replace('data:', '').trim()
      }

      if (data) {
        try {
          onMessage({ event, data: JSON.parse(data) })
        } catch {
          onMessage({ event, data: data as any })
        }
      }
    }
  }
}

三、Axios SSE Hook

composables/useSSEAxios.ts

ts 复制代码
import { ref } from 'vue'
import { requestSSE } from '@/services/sseAxios'

export function useSSEAxios<T = any>(url: string) {
  const data = ref<T[]>([])
  const loading = ref(false)
  const error = ref<Error | null>(null)

  const start = (payload?: any) => {
    loading.value = true
    error.value = null

    requestSSE<T>(
      url,
      (msg) => {
        if (msg.event === 'message') {
          data.value.push(msg.data)
        }
      },
      {
        method: 'POST',
        data: payload
      }
    ).catch(err => {
      error.value = err
    }).finally(() => {
      loading.value = false
    })
  }

  return { data, loading, error, start }
}

四、组件中使用(大模型对话)

vue 复制代码
<script setup lang="ts">
import { useSSEAxios } from '@/composables/useSSEAxios'

const { data, loading, start } = useSSEAxios<{
  role: string
  content: string
}>('/api/chat')

const send = () => {
  start({
    messages: [{ role: 'user', content: '你好' }]
  })
}
</script>

<template>
  <button @click="send" :disabled="loading">
    {{ loading ? '生成中...' : '发送' }}
  </button>

  <div v-for="(msg, i) in data" :key="i">
    {{ msg.content }}
  </div>
</template>

五、最终选择建议(记住这张表)

场景 选谁
简单通知 / 日志 ✅ EventSource
大模型 / AI ✅ Axios
需要 Token ✅ Axios
想省事 ✅ EventSource
公司项目 ✅ Axios

相关推荐
我不是外星人23 分钟前
有了 Harness Engineering ,真的还需要研发工程师吗?
前端·后端·ai编程
candyTong29 分钟前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
IT_陈寒3 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
Jackson__4 小时前
分享一个横向滚动案例,带悬停暂停,通用性很强
前端
MariaH4 小时前
git rebase的使用
前端
_柳青杨4 小时前
深入理解 JavaScript 事件循环
前端·javascript
阡陌Jony4 小时前
关于前端性能优化的一些问题:
前端
用户600071819105 小时前
【翻译】简化 TSRX
前端
IT乐手6 小时前
佛德角逼平西班牙,国足还有啥借口?
前端
JustHappy7 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试