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

相关推荐
2601_9657984719 分钟前
Arrow Unlocker HTML5 Game Review: Keep Web Arcade Players Coming Back
前端·html·html5
不简说1 小时前
JS 代码技巧 vol.10 — 20 个 V8 引擎原理,解释"为什么这样写快"
前端·javascript·面试
醉城夜风~1 小时前
HTML常用标签详解学习博客:从基础标签到页面结构完整掌握
前端·学习·html
OpenTiny社区1 小时前
拒绝臃肿!TinyRobot从“AI 输入框”到“AI输入控制台”的可扩展进化
前端·vue.js
淼澄研学2 小时前
PyTorch深度学习实战:5个核心方法从0到1构建神经网络
前端·数据库·python
英勇无比的消炎药2 小时前
Vue 工程接入 WebMCP SDK 最佳实践:从零构建智能前端应用
vue.js
MartinYeung52 小时前
[论文学习]WASP:面向提示注入攻击的Web代理安全性基准测试
前端·网络·学习
lemon_sjdk2 小时前
从 ServletRequest 到 Spring 抽象:Web 请求的底层基石与演化
java·前端·spring
程序员黑豆3 小时前
鸿蒙应用开发之状态变化通知:@Watch 装饰器详解与实战
前端·harmonyos
swipe3 小时前
13|(前端转全栈)支付成功不等于结束:回调、幂等、超时关单和状态竞争
前端·后端·面试