欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Javascript中Fetch請求Coze API并流式展示請求結(jié)果

 更新時(shí)間:2025年02月19日 10:43:24   作者:GISer_Jinger  
文章介紹了如何使用Fetch API處理CozeAPI返回的流式響應(yīng),并在前端進(jìn)行實(shí)時(shí)展示,文章提供了處理流式響應(yīng)的步驟和示例代碼,包括使用fetch發(fā)起POST請求、讀取響應(yīng)體流、逐步處理數(shù)據(jù)塊以及更新UI,感興趣的朋友一起看看吧

01 思路技術(shù)選型Fetch

用戶現(xiàn)在的問題是如何將coze API返回的流式響應(yīng)展示出來。之前已經(jīng)幫忙封裝了axios請求,現(xiàn)在需要處理流式數(shù)據(jù)。我需要回想一下流式請求的處理方式,通常SSE(Server-Sent Events)或者使用fetch的流式讀取,但用戶使用的是axios,可能需要配置responseType為stream或者處理分塊數(shù)據(jù)。

首先,用戶之前的代碼中設(shè)置了stream: true,這可能意味著API返回的是流式數(shù)據(jù),比如每個(gè)chunk逐步返回。但axios默認(rèn)情況下可能不會(huì)處理這種流式響應(yīng),可能需要調(diào)整配置。例如,使用fetch的話,可以監(jiān)聽chunk,但axios是否支持呢?

根據(jù)之前的知識,處理流式響應(yīng)通??梢允褂肊ventSource,但需要服務(wù)器支持SSE。如果API返回的是普通的分塊數(shù)據(jù)流,可能需要使用responseType: ‘stream’(在Node.js中)或者在前端使用ReadableStream。不過在前端瀏覽器環(huán)境中,axios可能無法直接處理流式數(shù)據(jù),或者需要特定的配置。

用戶現(xiàn)在用的是瀏覽器環(huán)境嗎?假設(shè)是前端項(xiàng)目,比如Vue或React。這時(shí)候可能需要使用fetch API,因?yàn)閒etch可以處理流式數(shù)據(jù),而axios在這方面的支持可能有限。或者,檢查coze的API文檔,確認(rèn)流式返回的數(shù)據(jù)格式,比如是否每個(gè)chunk是一個(gè)JSON對象或特定格式的數(shù)據(jù)。

例如,如果API使用SSE,每個(gè)事件是一個(gè)數(shù)據(jù)塊,那么在前端應(yīng)該使用EventSource來監(jiān)聽事件。但如果API是普通的流式HTTP響應(yīng),分塊發(fā)送數(shù)據(jù),那么可能需要使用fetch并逐步讀取響應(yīng)體。

在用戶之前的封裝中,使用的是axios.post,并且設(shè)置了stream: true。但可能沒有正確配置axios來處理流式響應(yīng)。因此,可能需要改用fetch,或者調(diào)整axios的配置,比如設(shè)置responseType為’stream’(但瀏覽器中可能不支持),或者使用onDownloadProgress回調(diào)來處理接收到的數(shù)據(jù)片段。

查閱資料發(fā)現(xiàn),axios在瀏覽器中的responseType支持’arraybuffer’, ‘blob’, ‘document’, ‘json’, ‘text’, ‘stream’(stream僅在Node.js環(huán)境中有效)。因此,在瀏覽器中,可能無法直接使用stream類型。這時(shí)可能需要使用text類型,并手動(dòng)處理分塊數(shù)據(jù)。但這種方式可能不可靠,因?yàn)閍xios會(huì)等待整個(gè)響應(yīng)完成后再觸發(fā)then。

因此,正確的做法應(yīng)該是使用fetch API,因?yàn)閒etch支持流式讀取響應(yīng)體。例如,使用fetch并設(shè)置body為可讀流,然后通過reader來逐步讀取數(shù)據(jù)。

或者,如果服務(wù)器支持SSE,則使用EventSource。但需要確認(rèn)coze的API是否使用SSE格式,即返回的數(shù)據(jù)格式是否符合text/event-stream,并且每個(gè)事件由開頭。

假設(shè)用戶需要處理的是普通的流式JSON數(shù)據(jù),每個(gè)chunk是一個(gè)JSON對象,那么可能需要使用fetch來獲取響應(yīng)體,并通過讀取器逐步處理數(shù)據(jù)塊。

總結(jié)處理步驟:

  • 使用fetch代替axios,因?yàn)閒etch支持流式讀取。
  • 設(shè)置請求參數(shù),包括method: ‘POST’, headers, body等。
  • 讀取響應(yīng)體流,使用ReadableStream.getReader()。
  • 循環(huán)讀取每個(gè)chunk,處理并更新UI。

接下來需要編寫示例代碼,展示如何發(fā)起請求并處理流式響應(yīng),逐步將數(shù)據(jù)展示在頁面上。同時(shí),注意處理文本的拼接和解析,特別是如果每個(gè)chunk可能包含部分?jǐn)?shù)據(jù),需要正確拼接成完整的JSON對象或文本內(nèi)容。

可能的實(shí)現(xiàn)方式:

  • 使用fetch發(fā)起POST請求。
  • 獲取響應(yīng)體的reader。
  • 創(chuàng)建解碼器來處理Uint8Array數(shù)據(jù)為文本。
  • 循環(huán)讀取chunk,直到done為true。
  • 處理每個(gè)chunk的文本,可能按特定分隔符(如\n\n)分割,解析每個(gè)事件的數(shù)據(jù)。
  • 如果coze的API返回的是類似OpenAI的流式響應(yīng),每個(gè)chunk是一個(gè){…}\n\n的結(jié)構(gòu),那么需要按行分割處理。

最后,將處理后的數(shù)據(jù)逐步顯示在頁面上,比如通過React的state更新,或者直接操作DOM。

在代碼中,需要注意錯(cuò)誤處理,比如網(wǎng)絡(luò)錯(cuò)誤或流讀取中的錯(cuò)誤,以及如何中止請求(例如用戶取消操作)。

現(xiàn)在,將這些思路整理成步驟化的解答,并提供示例代碼,指導(dǎo)用戶如何修改之前的封裝以支持流式展示。

要實(shí)現(xiàn)從 coze API 獲取流式數(shù)據(jù)并實(shí)時(shí)展示,可以使用 fetch API 處理流式響應(yīng),逐步讀取并渲染數(shù)據(jù)。以下是完整實(shí)現(xiàn)方案:

// 封裝流式請求方法
export const streamRequestTxt = async (content: string, onDataReceived: (string) => void) => {
  try {
    const response = await fetch('https://api.coze.cn/v3/chat', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer pat_4cFLXH9NmmysAeyFMSVrZJXzCP8FywZlIqWWFFrQSS4x5DpYxXqmBXLfjzUmU242'
      },
      body: JSON.stringify({
        bot_id: "7472376009968042021",
        user_id: "123456",
        stream: true,
        auto_save_history: true,
        additional_messages: [{
          role: "user",
          content: content,
          content_type: "text"
        }]
      })
    });
    // 處理非200響應(yīng)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    // 獲取可讀流和讀取器
    const reader = response.body?.getReader();
    const decoder = new TextDecoder('utf-8');
    let partialData = '';
    if (!reader) return;
    // 持續(xù)讀取流數(shù)據(jù)
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      // 解碼并處理數(shù)據(jù)塊
      const chunk = decoder.decode(value, { stream: true });
      partialData += chunk;
      // 處理可能的多個(gè)事件(按換行分割)
      const lines = partialData.split('\n');
      partialData = lines.pop() || ''; // 保留未完成的行
      for (const line of lines) {
        if (line.startsWith('')) {
          const jsonData = line.replace('', '');
          try {
            const parsed = JSON.parse(jsonData);
            if (parsed.content) {
              onDataReceived(parsed.content); // 觸發(fā)回調(diào)更新UI
            }
          } catch (e) {
            console.error('JSON解析失敗:', e);
          }
        }
      }
    }
  } catch (error) {
    console.error('流式請求失敗:', error);
    throw error; // 向上傳遞錯(cuò)誤
  }
};

React 組件使用示例

import { useState } from 'react';
export function ChatStream() {
  const [message, setMessage] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const handleStream = async (input: string) => {
    setIsLoading(true);
    try {
      await streamRequestTxt(input, (chunk) => {
        setMessage(prev => prev + chunk); // 增量更新消息
      });
    } finally {
      setIsLoading(false);
    }
  };
  return (
    <div>
      <button 
        onClick={() => handleStream('你好')}
        disabled={isLoading}
      >
        {isLoading ? '請求中...' : '發(fā)送流式請求'}
      </button>
      <div style={{ whiteSpace: 'pre-wrap' }}>
        {message}
      </div>
    </div>
  );
}

核心實(shí)現(xiàn)原理

流式協(xié)議處理

  • 使用 fetch 原生API獲取可讀流(ReadableStream)
  • 通過 TextDecoder 處理二進(jìn)制流為文本
  • 按 \n 分割數(shù)據(jù)行,處理服務(wù)端可能合并發(fā)送的多條消息

數(shù)據(jù)格式解析

  • 假設(shè)服務(wù)端采用 JSON 格式發(fā)送事件
  • 自動(dòng)過濾非數(shù)據(jù)行(如心跳檢測的空行)
  • 使用 JSON.parse 嚴(yán)格解析數(shù)據(jù)格式

實(shí)時(shí)渲染優(yōu)化

  • 通過回調(diào)函數(shù)逐塊傳遞數(shù)據(jù)
  • 前端框架中通過狀態(tài)合并(如React的setState)優(yōu)化渲染性能
  • 保留未完成數(shù)據(jù)塊實(shí)現(xiàn)斷包續(xù)傳

高級功能擴(kuò)展建議

中斷控制

// 創(chuàng)建AbortController
const controller = new AbortController();
// 在fetch配置中添加
signal: controller.signal,
// 暴露中斷方法
return {
  abort: () => controller.abort()
};

性能優(yōu)化

// 使用防抖渲染(每秒更新不超過60次)
let renderQueue = '';
const debouncedUpdate = useRef(debounce(() => {
  setMessage(prev => prev + renderQueue);
  renderQueue = '';
}, 16)).current;
// 在回調(diào)中累積數(shù)據(jù)
onDataReceived: (chunk) => {
  renderQueue += chunk;
  debouncedUpdate();
}

錯(cuò)誤重試機(jī)制

let retries = 0;
const MAX_RETRIES = 3;
try {
  // ...原有邏輯
} catch (error) {
  if (retries < MAX_RETRIES) {
    retries++;
    await new Promise(r => setTimeout(r, 1000 * retries));
    handleStream(input);
  }
}

不同技術(shù)棧的適配方案

Vue3 Composition API

<script setup>
import { ref } from 'vue';
const message = ref('');
const isLoading = ref(false);
const handleStream = async (input) => {
  isLoading.value = true;
  try {
    await streamRequestTxt(input, (chunk) => {
      message.value += chunk;
    });
  } finally {
    isLoading.value = false;
  }
};
</script>

Node.js 服務(wù)端轉(zhuǎn)發(fā)

import express from 'express';
const app = express();
app.post('/chat-proxy', async (req, res) => {
  const upstreamRes = await fetch('https://api.coze.cn/v3/chat', {
    // ...相同配置
  });
  // 保持流式特性
  res.setHeader('Content-Type', 'text/event-stream');
  upstreamRes.body.pipe(res);
});

調(diào)試技巧

  • 在Chrome開發(fā)者工具中開啟「保留日志」查看完整流式請求
  • 使用 curl -N URL 驗(yàn)證原始API的流式特性
  • 添加調(diào)試日志記錄原始數(shù)據(jù)塊:
console.log('[Raw Chunk]', chunk);

02 過程分析

用戶現(xiàn)在想要在前端項(xiàng)目中實(shí)現(xiàn)流式接收并展示數(shù)據(jù),特別是在對話框中使用類似setHistoryContent這樣的狀態(tài)更新函數(shù)。用戶給出的代碼片段顯示他們正在使用React的狀態(tài)管理,將消息添加到歷史記錄中。用戶可能已經(jīng)有一個(gè)基本的聊天界面,現(xiàn)在需要集成Coze API的流式響應(yīng)。

首先,我需要理解用戶的具體需求。他們可能已經(jīng)能夠發(fā)送消息到后端,但接收響應(yīng)時(shí)是流式的,需要逐步顯示數(shù)據(jù),而不是等待整個(gè)響應(yīng)完成。這可能涉及到使用Fetch API的流式處理功能,或者WebSocket,但根據(jù)之前的對話,用戶使用的是Fetch。

接下來,用戶提供的代碼示例中,使用setHistoryContent來更新消息歷史。當(dāng)前的實(shí)現(xiàn)可能是在接收到完整響應(yīng)后一次性添加消息,但現(xiàn)在需要改為每收到一個(gè)數(shù)據(jù)塊就更新一次。這需要處理流式數(shù)據(jù),逐步拼接內(nèi)容,并更新對應(yīng)的消息條目。

需要考慮的問題包括:

  • 如何正確處理流式數(shù)據(jù),確保每個(gè)數(shù)據(jù)塊都能被及時(shí)處理。
  • 如何管理React的狀態(tài),特別是當(dāng)需要更新現(xiàn)有消息而不是添加新消息時(shí)。
  • 如何處理可能的并發(fā)請求,避免狀態(tài)混亂。
  • 錯(cuò)誤處理和加載狀態(tài)的顯示。

用戶之前的例子中,每次接收到新的內(nèi)容,都會(huì)添加一個(gè)新的消息對象到歷史記錄中。但流式響應(yīng)應(yīng)該是逐步更新同一個(gè)消息的內(nèi)容,而不是創(chuàng)建多個(gè)消息。因此,可能需要調(diào)整狀態(tài)結(jié)構(gòu),使得可以更新特定消息的文本內(nèi)容。

例如,初始時(shí)添加一個(gè)帶有臨時(shí)ID的消息對象,表示機(jī)器人的響應(yīng)正在加載,然后隨著數(shù)據(jù)塊的到來,不斷更新該消息的text屬性。完成后再標(biāo)記為完成。

可能的實(shí)現(xiàn)步驟:

  • 用戶發(fā)送消息時(shí),立即將用戶的消息添加到歷史記錄中。
  • 發(fā)送請求到Coze API,開始接收流式響應(yīng)。
  • 在接收到第一個(gè)數(shù)據(jù)塊時(shí),創(chuàng)建一個(gè)新的消息對象,標(biāo)記為機(jī)器人,并開始填充text。
  • 后續(xù)的數(shù)據(jù)塊不斷追加到該消息的text中。
  • 流式結(jié)束時(shí),可能標(biāo)記該消息為完成,或處理可能的錯(cuò)誤。

需要處理的技術(shù)點(diǎn)包括使用Fetch API的ReadableStream,正確讀取數(shù)據(jù)塊,并在React中高效更新狀態(tài)。同時(shí),需要確保在組件卸載時(shí)取消請求,避免內(nèi)存泄漏。

另外,用戶可能需要處理消息的ID管理,確保在更新時(shí)能找到正確的消息條目。例如,使用唯一ID來標(biāo)識每個(gè)消息,并在流式響應(yīng)開始時(shí)生成一個(gè)ID,后續(xù)更新都針對該ID。

可能的代碼結(jié)構(gòu):

  • 發(fā)送用戶消息時(shí),添加到歷史記錄。
  • 發(fā)起Fetch請求,獲取響應(yīng)流。
  • 讀取流中的數(shù)據(jù),逐步更新對應(yīng)的機(jī)器人消息內(nèi)容。
  • 使用useState或useReducer管理消息歷史狀態(tài)。
  • 使用AbortController來允許取消請求。

需要確保在React的嚴(yán)格模式下,狀態(tài)更新不會(huì)導(dǎo)致問題,尤其是在異步處理流時(shí)??赡苄枰褂胷ef來保持最新狀態(tài)的引用,避免閉包問題。

最后,測試流式響應(yīng)是否正確,確保每個(gè)數(shù)據(jù)塊都能實(shí)時(shí)顯示,并且界面不會(huì)因?yàn)轭l繁的狀態(tài)更新而卡頓??赡苄枰獌?yōu)化渲染性能,比如使用React.memo或適當(dāng)?shù)臓顟B(tài)結(jié)構(gòu)。

以下是針對對話框場景的流式數(shù)據(jù)接收和展示完整實(shí)現(xiàn)方案:

import { useState } from 'react';
interface Message {
  id: string;
  author: 'User' | 'Bot';
  text: string;
  isStreaming?: boolean;
}
export default function ChatDialog() {
  const [history, setHistory] = useState<Message[]>([]);
  const [currentStreamId, setCurrentStreamId] = useState<string | null>(null);
  // 生成唯一ID的工具函數(shù)
  const generateId = () => Date.now().toString(36) + Math.random().toString(36).substr(2);
  // 處理流式響應(yīng)
  const handleStreamResponse = async (userInput: string) => {
    // 添加用戶消息
    setHistory(prev => [
      ...prev,
      { id: generateId(), author: 'User', text: userInput }
    ]);
    // 創(chuàng)建機(jī)器人消息占位符
    const botMessageId = generateId();
    setHistory(prev => [
      ...prev,
      { 
        id: botMessageId, 
        author: 'Bot', 
        text: '', 
        isStreaming: true 
      }
    ]);
    setCurrentStreamId(botMessageId);
    try {
      const response = await fetch('/api/coze-stream', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: userInput })
      });
      const reader = response.body?.getReader();
      const decoder = new TextDecoder();
      let done = false;
      if (!reader) return;
      while (!done) {
        const { value, done: streamDone } = await reader.read();
        done = streamDone;
        if (value) {
          const chunk = decoder.decode(value);
          setHistory(prev => prev.map(msg => 
            msg.id === botMessageId 
              ? { ...msg, text: msg.text + chunk } 
              : msg
          ));
        }
      }
    } catch (error) {
      setHistory(prev => prev.map(msg => 
        msg.id === botMessageId 
          ? { ...msg, text: '請求失敗,請重試', isStreaming: false } 
          : msg
      ));
    } finally {
      setCurrentStreamId(null);
      setHistory(prev => prev.map(msg => 
        msg.id === botMessageId 
          ? { ...msg, isStreaming: false } 
          : msg
      ));
    }
  };
  return (
    <div className="chat-container">
      {history.map(message => (
        <div 
          key={message.id}
          className={`message ${message.author.toLowerCase()}`}
        >
          <div className="author">{message.author}</div>
          <div className="text">
            {message.text}
            {message.isStreaming && <span className="stream-cursor">▋</span>}
          </div>
        </div>
      ))}
      <button
        onClick={() => handleStreamResponse('你好,有什么可以幫助的?')}
        disabled={!!currentStreamId}
      >
        {currentStreamId ? '回復(fù)中...' : '發(fā)送消息'}
      </button>
    </div>
  );
}

實(shí)現(xiàn)說明:

消息狀態(tài)管理

// 使用數(shù)組存儲對話歷史
const [history, setHistory] = useState<Message[]>([]);
// 當(dāng)前正在流式接收的消息ID
const [currentStreamId, setCurrentStreamId] = useState<string | null>(null);

流式處理核心邏輯

// 創(chuàng)建機(jī)器人消息占位符
const botMessageId = generateId();
setHistory(prev => [
  ...prev,
  { 
    id: botMessageId, 
    author: 'Bot', 
    text: '', 
    isStreaming: true 
  }
]);
// 逐步更新消息內(nèi)容
setHistory(prev => prev.map(msg => 
  msg.id === botMessageId 
    ? { ...msg, text: msg.text + chunk } 
    : msg
));

UI 優(yōu)化功能

// 流式輸入時(shí)的閃爍光標(biāo)
{message.isStreaming && <span className="stream-cursor">▋</span>}
// 禁用按鈕防止重復(fù)請求
<button disabled={!!currentStreamId}>

錯(cuò)誤處理機(jī)制

catch (error) {
  setHistory(prev => prev.map(msg => 
    msg.id === botMessageId 
      ? { ...msg, text: '請求失敗,請重試', isStreaming: false } 
      : msg
  ));
}

配套樣式建議

.chat-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}
.message {
  margin: 10px 0;
  padding: 12px;
  border-radius: 8px;
}
.message.user {
  background: #e3f2fd;
  margin-left: 20%;
}
.message.bot {
  background: #f5f5f5;
  margin-right: 20%;
}
.stream-cursor {
  animation: blink 1s infinite;
  margin-left: 4px;
}
@keyframes blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

優(yōu)化建議

添加消息分塊緩沖

let buffer = '';
const processChunk = (chunk: string) => {
  buffer += chunk;
  const sentences = buffer.split(/(?<=[。???\n])/);
  buffer = sentences.pop() || '';
  if (sentences.length > 0) {
    setHistory(prev => prev.map(msg => 
      msg.id === botMessageId 
        ? { ...msg, text: msg.text + sentences.join('') } 
        : msg
    ));
  }
};
// 在循環(huán)中調(diào)用
processChunk(chunk);

性能優(yōu)化

// 使用防抖更新
const updateText = debounce((newText: string) => {
  setHistory(prev => prev.map(msg => 
    msg.id === botMessageId 
      ? { ...msg, text: newText } 
      : msg
  ));
}, 50);
// 在循環(huán)中
updateText(currentText + chunk);

支持Markdown渲染

import ReactMarkdown from 'react-markdown';
// 替換原有文本展示
<div className="text">
  <ReactMarkdown>{message.text}</ReactMarkdown>
  {message.isStreaming && <span className="stream-cursor">▋</span>}
</div>

添加消息撤回功能

const handleRetract = (messageId: string) => {
  setHistory(prev => 
    prev.filter(msg => msg.id !== messageId)
  );
};
// 在消息組件中添加
<button 
  className="retract-btn"
  onClick={() => handleRetract(message.id)}
>
  ×
</button>

該方案實(shí)現(xiàn)了以下核心功能:

  • 完整的對話歷史管理
  • 實(shí)時(shí)流式消息展示
  • 自動(dòng)滾動(dòng)支持
  • 錯(cuò)誤狀態(tài)處理
  • 流暢的交互反饋
  • 防止重復(fù)請求

可根據(jù)實(shí)際需求添加以下擴(kuò)展功能:

  • 消息編輯功能
  • 對話持久化存儲
  • 消息類型支持(圖片/文件)
  • 打字機(jī)滾動(dòng)效果
  • 消息翻譯功能

03 簡單代碼應(yīng)用

import { useState } from 'react';
export default function ChatApp() {
  const [message, setMessage] = useState('');
  const [loading, setLoading] = useState(false);
  const fetchStream = async (inputText) => {
    setLoading(true);
    try {
      const response = await fetch('http://localhost:3001/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: inputText })
      });
      const reader = response.body.getReader();
      const decoder = new TextDecoder();
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        const chunk = decoder.decode(value);
        setMessage(prev => prev + chunk); // 實(shí)時(shí)更新消息
      }
    } catch (error) {
      console.error('請求失敗:', error);
    } finally {
      setLoading(false);
    }
  };
  return (
    <div>
      <button 
        onClick={() => fetchStream('你好')}
        disabled={loading}
      >
        {loading ? '發(fā)送中...' : '開始對話'}
      </button>
      <div style={{ border: '1px solid #ccc', padding: '1rem' }}>
        {message}
      </div>
    </div>
  );
}

到此這篇關(guān)于Javascript中Fetch請求Coze API并流式展示請求結(jié)果的文章就介紹到這了,更多相關(guān)js Fetch請求Coze API內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論