用fetch-event-source处理流式消息:Vue 3中实现openAI/DeepSeek的实时输出

microsoft/fetch-event-source 是一个由微软开发的开源库,旨在提供比浏览器原生 EventSource API 更强大的 Server-Sent Events (SSE) 请求处理能力

核心优势

  • ‌完全兼容 Fetch API‌:支持所有 HTTP 方法和自定义头部
  • 智能重试控制‌:开发者可以完全控制连接中断时的重试策略
  • 灵活的错误处理‌:提供完善的错误处理机制

一、安装依赖

javascript 复制代码
npm install @microsoft/fetch-event-source

二、基本使用

javascript 复制代码
import { fetchEventSource } from '@microsoft/fetch-event-source';

await fetchEventSource('/api/sse-endpoint', {
  method: 'GET', // 支持 GET/POST/PUT 等 HTTP 方法
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer your-token'
  },
  body: JSON.stringify({
    query: '你的查询内容'
  })
  onopen(response) {
    if (response.ok) {
      console.log('SSE连接成功');
    } else {
      throw new Error(`连接失败: ${response.status}`);
    }
  },
  onmessage(event) {
    console.log('收到消息:', event.data); //这里的消息就是流式输出的
  },
  onclose() {
    console.log('SSE连接关闭');
  },
  onerror(err) {
    console.error('发生错误:', err);
    throw err; // 抛出错误会停止重试
  }
});

三、封装成hooks

javascript 复制代码
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { ref, onUnmounted } from 'vue';

interface EventSourceOptions {
  url: string;
  method?: 'GET' | 'POST';
  body?: any;
  onMessage?: (data: string) => void;
  onOpen?: (response: Response) => void;
  onError?: (err: any) => void;
}

export function useEventSource() {
  const error = ref<any>(null);
  const isLoading = ref<boolean>(false);
  let controller: AbortController | null = null;

  const fetchStream = async (options: EventSourceOptions) => {
    isLoading.value = true;
    error.value = null;
    controller = new AbortController();

    try {
      await fetchEventSource(options.url, {
        method: options.method || 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Accept': "text/event-stream",
        },
        body: options.body ? JSON.stringify(options.body) : undefined,
        signal: controller.signal,
        // 建立链接
        onopen: async (response) => {
          if (response.ok && response.status == 200) {
            options.onOpen?.(response);
          }
        },
        // 接收消息
        onmessage: (event) => {
          if (event.data) {
            options.onMessage?.(JSON.parse(event.data));
          }
        },
        onerror: (err) => {
          options.onError?.(err);
          // 必须抛出错误才会停止重试
          throw err;
        },
        onclose: () => {
          isLoading.value = false;
        }
      });
    } catch (err) {
    	 console.log("链接失败");
      error.value = err;
      isLoading.value = false;
    }
  };

 // 主动中断链接
  const disconnect= () => {
    if (controller) {
      controller.abort();
      isLoading.value = false;
    }
  };

  onUnmounted(() => {
    disconnect();
  });

  return {
      isLoading,
      fetchStream,
      disconnect,
      onerror,
      onclose,
  };
}

四、使用hooks

javascript 复制代码
<template>
  <div>
    <button @click="sendMessage" :disabled="isLoading">
      {{ isLoading ? '发送中...' : '发送消息' }}
    </button>
    
    <div class="stream-content">
      {{ data }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useEventSource } from '@/composables/useEventSource';

const { isLoading, fetchStream } = useEventSource();

const data = ref('')

const sendMessage = async () => {
  await fetchStream({
    url: '/api/chat',
    method: 'POST',
    body: {
      content: '你好,请帮我回答这个问题'
    },
    onMessage: (content) => {
      console.log('收到数据:', content);
      data.value += content
    },
    onOpen: (response) => {
      console.log('连接已建立');
    },
    onError: (err) => {
      console.error('连接错误:', err);
    }
  });
};
</script>
相关推荐
liangshanbo121510 小时前
面试题:什么是 Reflect?它解决了什么问题?
vue3·reflect
RuoyiOffice12 小时前
SpringBoot3+Vue3 低代码业务表单:从拖拽设计、独立建表到审批与数据查询
spring boot·vue3·数据建模·flowable·版本管理·低代码表单·ruoyi office
RuoyiOffice12 小时前
SpringBoot3 企业薪酬核算:考勤、绩效与薪资规则如何算出一张可解释的工资单
spring boot·vue3·规则引擎·hrm·ruoyi office·薪酬核算·薪资试算
RuoyiOffice7 天前
2026最推荐基于SpringBoot+Vue3的项目管理经营一体化:从商机、合同、任务与工时走到回款和利润
spring boot·项目管理·vue3·crm·合同管理·ruoyi office·项目经营
RuoyiOffice8 天前
SpringBoot+Vue3 节日主题换肤实战:一条参数换全站配色,节后自动还原
spring boot·vue3·spring boot 3·vben admin·ruoyi office·节日主题·换肤方案
RuoyiOffice10 天前
SpringBoot3+Vue3 流程抄送已读:打开详情消红点,待办与知会怎么分开
spring boot·vue3·flowable·spring boot 3·ruoyi office·流程抄送·已读回执
志尊宝10 天前
Vue3 零基础每日笔记(038):亲手封装 useDebounceFn 与 useThrottleFn——高频事件的流量阀
前端·javascript·vue·html·vue3
志尊宝11 天前
Vue3 零基础每日笔记(043):defineModel 与 defineExpose 的 TS 用法——3.4+ 新 API 进阶
前端·javascript·vue·html·vue3
RuoyiOffice12 天前
SpringBoot3+Vue3+Flowable 企业审批平台全链路:从表单设计、节点权限到移动审批怎么落地
spring boot·vue3·uniapp·springboot3·flowable·审批系统·ruoyi office
RuoyiOffice12 天前
SpringBoot3+Vue3 企业级管理平台架构:多租户、RBAC、数据权限、工作流与微服务一次讲透
spring boot·vue3·springboot3·rbac·多租户·数据权限·ruoyi office