AI流行的背景下,很多项目也开始接入大模型,而前端与大模型服务对接的核心交互就是 SSE(server-sent events) ,通过 SSE 可以实现大模型的流式输出,@microsoft/fetch-event-source 这个工具为发起 SSE 请求提供了完善的 Fetch API,本文主要是基于这个工具提供一个通用、可复用的 SSE 请求封装方法,涵盖参数配置、流式数据处理、异常捕获、主动中断等核心场景,帮助项目快速落地大模型流式交互功能,避免重复造轮子。
安装依赖
npm install @microsoft/fetch-event-source
封装方法
import { EventStreamContentType, FetchEventSourceInit, fetchEventSource } from '@microsoft/fetch-event-source'
/**
* 通用的 sse 请求方法
*
* @export
* @param {string} url 接口地址
* @param {*} body 请求体
* @param {FetchEventSourceInit} eventInit 事件配置,参考文档:https://www.npmjs.com/package/@microsoft/fetch-event-source#usage
* @return {Promise<AbortController>} 返回中止控制器,可以在组件销毁或需要中止事件流时调用 abort() 中断事件流
*/
export async function sseFetch(url: string, body: any, eventInit: FetchEventSourceInit): Promise<AbortController> {
let messageReceived = false
return new Promise((resolve, reject) => {
const ctrl = new AbortController()
const onerror = function (error: any) {
let errorMsg = typeof error === 'string' ? error : error?.message || error?.title || 'Content Render Failed!'
if (error?.name) {
errorMsg = error.name + ':' + errorMsg
}
console.error(error)
if (eventInit.onerror) {
eventInit.onerror(new Error(errorMsg))
} else if (eventInit.onmessage) {
eventInit.onmessage({ event: 'error', data: errorMsg, id: '__error__' })
}
if (!messageReceived) {
reject(error)
}
messageReceived = true
ctrl.abort()
}
if (eventInit.onmessage) {
const onmessage = eventInit.onmessage
eventInit.onmessage = function (ev) {
try {
onmessage(ev)
if (!messageReceived) {
resolve(ctrl)
}
} catch (e) {
onerror(e)
} finally {
messageReceived = true
}
}
}
fetchEventSource(url, {
method: 'POST',
body: typeof body === 'string' ? body : JSON.stringify(body),
headers: {
accept: '*/*',
},
credentials: 'include',
signal: ctrl.signal,
openWhenHidden: true,
fetch(input, init) {
return fetch(input, { ...init, signal: ctrl.signal })
.then(async res => {
const contentType = res.headers.get('content-type')
if (!contentType?.includes(EventStreamContentType)) {
const json = contentType === 'application/json' ? await res.json() : await res.text()
onerror(json)
}
return res
})
.catch(err => {
onerror(err)
return Promise.reject(err)
})
},
...eventInit,
}).catch(e => reject(e))
})
}
使用示例
// 调用 sse 请求并获取流式输出文本
this.abortController = await sseFetch(url, reqData, {
headers: {
'Content-Type': 'application/json', // 如果接口有要求 contentType 需要添加请求头,否则就不需要
},
onopen(res) {
// 事件流开启并连通后的回调
console.log('---stream open---', res)
},
onmessage(msg) {
// 接收消息,一般来说实现这个回调就足够了
console.log('---msg received---', msg)
},
onclose() {
// 事件流被关闭的回调
console.log('---stream close---')
},
onerror(err) {
// 请求异常的回调
console.error('---stream error---', err)
}
})
// 停止 sse 请求
this.abortController.stop()