You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Next.js ChatGPT示例中添加取消按钮终止流式请求循环

在Next.js AI ChatGPT示例中终止流式请求的正确方法

你之前尝试用cancelled状态变量终止while循环无效,核心原因是React状态更新的闭包特性:循环运行在组件某次渲染的闭包中,引用的是该次渲染时的cancelled初始值,后续状态更新不会同步到这个闭包里的变量,因此循环无法感知到状态变化。

正确的实现方式是使用AbortController直接中断底层的流式网络请求,具体步骤如下:

1. 初始化AbortController引用

用useRef保存AbortController实例,避免组件重渲染时丢失引用:

import { useRef, useState } from 'react';

export default function ChatComponent() {
  // 用于控制请求取消的控制器
  const abortControllerRef = useRef(new AbortController());
  const [isCancelled, setIsCancelled] = useState(false);
  // 其他状态(如聊天消息、加载状态等)
  const [messages, setMessages] = useState([]);

2. 发起请求时关联Signal

在调用fetch发起流式请求时,将控制器的signal传入请求配置,让请求和控制器绑定:

const sendMessage = async (inputMessage) => {
  // 发起新请求前,先取消未完成的旧请求
  abortControllerRef.current.abort();
  // 创建新的控制器(一个控制器只能用一次)
  abortControllerRef.current = new AbortController();
  setIsCancelled(false);

  try {
    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        messages: [...messages, { role: 'user', content: inputMessage }]
      }),
      // 关联取消信号
      signal: abortControllerRef.current.signal
    });

    if (!response.ok) {
      throw new Error('请求失败');
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let done = false;
    let newMessageContent = '';

    // 处理流式响应
    while (!done) {
      const { value, done: doneReading } = await reader.read();
      done = doneReading;
      if (value) {
        const chunk = decoder.decode(value);
        // 拼接流式返回的内容
        newMessageContent += chunk;
        // 更新UI
        setMessages(prev => [
          ...prev.slice(0, -1),
          { role: 'assistant', content: newMessageContent }
        ]);
      }
    }
  } catch (error) {
    if (error.name === 'AbortError') {
      // 请求被取消后的处理,比如清空未完成的消息
      setMessages(prev => prev.slice(0, -1));
      console.log('请求已取消');
    } else {
      // 其他错误处理
      console.error('请求出错:', error);
    }
  }
};

3. 实现取消按钮

点击按钮时调用控制器的abort()方法,同时更新状态:

<button
  onClick={() => {
    abortControllerRef.current.abort();
    setIsCancelled(true);
  }}
  disabled={isCancelled}
>
  取消生成
</button>

关键说明

  • AbortController会直接中断底层的网络连接,让reader.read()立即抛出AbortError,从而终止while循环,这比单纯依赖状态变量更可靠。
  • 每次发起新请求前要创建新的AbortController实例,因为一个控制器只能触发一次取消操作。

内容的提问来源于stack exchange,提问作者Peter Kellner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 03:08:17