举个例子,在写llm的chat的时候,经常会出现需要取消请求的场景。

如何在前端取消请求,涉及到一个接口:AbortController.AbortController() - Web API 接口参考 | MDN

在原生的js的写法,参考mdn的写法。

let controller;
const url = "video.mp4";
 
const downloadBtn = document.querySelector(".download");
const abortBtn = document.querySelector(".abort");
 
downloadBtn.addEventListener("click", fetchVideo);
 
abortBtn.addEventListener("click", () => {
  if (controller) {
    controller.abort();
    controller = null;
    console.log("Download aborted");
  }
});
 
function fetchVideo() {
  controller = new AbortController();
  const signal = controller.signal;
  fetch(url, { signal })
    .then((response) => {
      console.log("Download complete", response);
    })
    .catch((err) => {
      console.error(`Download error: ${err.message}`);
    });
}
 

在react的写法

import React, { useState, useEffect } from 'react';
 
const RequestComponent = () => {
  const [responseData, setResponseData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  const [controller, setController] = useState(null);
 
  useEffect(() => {
	// 组件被卸载的时候,取消请求
    return () => {
      if (controller) {
        controller.abort();
      }
    };
  }, [controller]);
 
  const fetchData = async () => {
    setLoading(true);
    setError(null);
 
    const abortController = new AbortController();
    setController(abortController);
 
    try {
      const response = await fetch('https://api.example.com/data', {
        signal: abortController.signal,
      });
 
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
 
      const data = await response.json();
      setResponseData(data);
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('Request canceled by user');
      } else {
        setError(error);
      }
    } finally {
      setLoading(false);
    }
  };
 
  const cancelRequest = () => {
    if (controller) {
      controller.abort();
    }
  };
 
  return (
    <div>
      <button onClick={fetchData} disabled={loading}>
        {loading ? 'Loading...' : 'Fetch Data'}
      </button>
      <button onClick={cancelRequest} disabled={!loading}>
        Cancel Request
      </button>
      {error && <div>Error: {error.message}</div>}
      {responseData && <div>Data: {JSON.stringify(responseData)}</div>}
    </div>
  );
};
 
export default RequestComponent;
 

在solidjs中的写法,可以参考diu老师的GitHub - anse-app/chatgpt-demo: Minimal web UI for ChatGPT.

import { Index, Show, createEffect, createSignal, onCleanup, onMount } from 'solid-js'
import { useThrottleFn } from 'solidjs-use'
import { generateSignature } from '@/utils/auth'
import IconClear from './icons/Clear'
import MessageItem from './MessageItem'
import SystemRoleSettings from './SystemRoleSettings'
import ErrorMessageItem from './ErrorMessageItem'
import type { ChatMessage, ErrorMessage } from '@/types'
 
export default () => {
  const [controller, setController] = createSignal<AbortController>(null)
 
 
  const requestWithLatestMessage = async() => {
    setLoading(true)
    setCurrentAssistantMessage('')
    setCurrentError(null)
    const storagePassword = localStorage.getItem('pass')
    try {
      const controller = new AbortController()
      setController(controller)
      const requestMessageList = messageList().slice(-maxHistoryMessages)
      if (currentSystemRoleSettings()) {
        requestMessageList.unshift({
          role: 'system',
          content: currentSystemRoleSettings(),
        })
      }
      const timestamp = Date.now()
      const response = await fetch('/api/generate', {
        method: 'POST',
        body: JSON.stringify({
          messages: requestMessageList,
          time: timestamp,
          pass: storagePassword,
          sign: await generateSignature({
            t: timestamp,
            m: requestMessageList?.[requestMessageList.length - 1]?.content || '',
          }),
          temperature: temperature(),
        }),
        signal: controller.signal,
      })
      if (!response.ok) {
        const error = await response.json()
        console.error(error.error)
        setCurrentError(error.error)
        throw new Error('Request failed')
      }
      const data = response.body
      if (!data)
        throw new Error('No data')
 
      const reader = data.getReader()
      const decoder = new TextDecoder('utf-8')
      let done = false
 
      while (!done) {
        const { value, done: readerDone } = await reader.read()
        if (value) {
          const char = decoder.decode(value)
          if (char === '\n' && currentAssistantMessage().endsWith('\n'))
            continue
 
          if (char)
            setCurrentAssistantMessage(currentAssistantMessage() + char)
 
          isStick() && instantToBottom()
        }
        done = readerDone
      }
    } catch (e) {
      console.error(e)
      setLoading(false)
      setController(null)
      return
    }
    archiveCurrentMessage()
    isStick() && instantToBottom()
  }
 
	const stopStreamFetch = () => {
		if (controller()) {
			controller().abort()
			...
		}
	}
  
 
  return (
    ...
  )
}