给AI对话加「停止生成」按钮:abort SSE实战

「停止生成」这四个字的按钮,看着简单,我做的时候连踩三个坑。用户点一下要立刻停,还得停得干净------别让请求在后台偷偷跑完烧 token,别让停完之后迟到的数据又冒出来。记一篇实操,全是血泪。

AbortController 是底座

fetch 的中断靠 AbortController,把它的 signal 传进去,调 abort() 就能掐断连接:

javascript 复制代码
const ctrl = new AbortController();

async function send(prompt: string) {
  const res = await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify({ prompt }),
    signal: ctrl.signal, // 关键
  });
  const reader = res.body!.getReader();
  // ...读流
}

// 按钮点击
function onStop() {
  ctrl.abort();
}

注意 ctrl 必须每轮对话新建一个------AbortController 是一次性的,abort 过的实例再用,下一次请求一发出去就立刻被中止。我第一版把它提到组件外当单例,结果点过一次停止后,后面所有消息都发不出去,自己还纳闷半天。

坑一:读流的循环要会处理 abort 抛错

abort() 之后,正在 await reader.read() 的那次会抛 AbortError。不 catch 的话控制台一片飘红,看着像崩了。得把它当正常流程吞掉:

javascript 复制代码
try {
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    handleChunk(decode(value));
  }
} catch (e) {
  if ((e as Error).name === 'AbortError') {
    // 用户主动停的,不是错误,静默收尾
  } else {
    throw e;
  }
}

AbortError 和真错误分开,是这块代码的体面所在。否则用户每点一次停止,监控就报一条 error,误报能把你淹了。

坑二:React 里 controller 存哪

存 state 会有闭包陷阱------按钮拿到的可能是旧的 controller。用 useRef 存当前这轮的实例才稳:

ini 复制代码
const ctrlRef = useRef<AbortController | null>(null);

async function send(prompt: string) {
  ctrlRef.current?.abort(); // 顺手把上一轮没停的也掐了
  const ctrl = new AbortController();
  ctrlRef.current = ctrl;
  // fetch with ctrl.signal
}

const onStop = () => ctrlRef.current?.abort();

顺带一个体验细节:用户在 AI 还在吐字时又发了条新消息,本质上也该停掉上一轮,所以 send 开头先 abort 一次,一举两得。

坑三:前端 abort,后端不一定停

这个最隐蔽。AbortController 断的只是浏览器到服务端的连接,后端那个调大模型的请求未必跟着停------很多实现里它会闷头把整段生成跑完,token 照烧。我抓包发现点了停止之后,后端日志里那次 completion 还在续,账单不会骗人。

解决得靠后端配合:服务端检测到客户端连接断开(Node 里是 req.on('close'))时,把对上游模型的请求也 abort 掉。前端这边我额外补了个 keepalive 的打点请求,显式告诉后端「这轮我不要了」,双保险。纯前端是兜不住这块成本的,别指望一个 abort 解决所有问题。

php 复制代码
const onStop = () => {
  ctrlRef.current?.abort();
  // 显式通知后端释放上游
  fetch('/api/chat/cancel', { method: 'POST', body: JSON.stringify({ id }), keepalive: true });
};

我用的模型服务本身在 MaaS 那层就支持取消上游生成,省了我自己去管算力释放------前端发个信号,后面停不停干净是平台的事。这种「现成 API + 平台兜底」的活,讯飞这类 MaaS 帮我把后端那截脏活担了,我专注前端这点交互。

你们的停止按钮,后端真的停了吗?建议抓个包看看账单,评论区聊聊有没有人也踩过这个隐形坑。

相关推荐
职场的momo2 小时前
用户讨论:算法开发Offer决赛:大疆纯后端Agent与网易游戏测试,对内AI跳槽难?
人工智能·游戏·跳槽
wish3662 小时前
EmployeeAssistant 智能问答服务:基于 pgvector 与 LLM 的企业知识库助手
人工智能·语言模型·自然语言处理·local llm
检信智能3 小时前
检信 Allemotion 多模态融合技术,解析非接触情绪采集的底层逻辑
人工智能·语音识别
RoboWizard3 小时前
固态硬盘对游戏帧数有影响吗?
人工智能
2301_768103496 小时前
AI视频创作Agent实战03:DeepSeek文案裂变与草稿版本控制
人工智能
火山引擎开发者社区6 小时前
Anker 首届黑客松挑战赛|9 月 7 日报名启动
人工智能
Dawson Zhu8 小时前
工作流与 Agent 的工程选型:从“控制权归属“看 LLM 应用架构
人工智能·语言模型·架构·aigc·agi
计算机源码社8 小时前
【大数据项目实战】基于大数据的影视内容生态综合质量分析与可视化-基于数据挖掘的影视内容类型共现与口碑聚类分析系统
大数据·人工智能·python·数据挖掘·数据分析·毕业设计·课程设计
C^h8 小时前
pytorch 适合初学者 0基础学习
人工智能·pytorch·python
Rocky Ding*9 小时前
【三年面试五年模拟】2026-09-06 拼多多 AI Agent研发岗秋招笔试4道算法题完整题解
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·拼多多