Fetch API 允許你跨網絡獲取資源,真正好的是,瀏覽器最近增加了將 fetch 響應作為可讀流使用的能力。
這是一個簡單明瞭的fetch+stream輸出的示例:
try {
// 定義手動停止接口
abortController.value = new AbortController();
// 發送請求
let response = await fetch(url, {
method: 'post',
headers: {
Authorization: '你的token',
'Content-Type': 'application/json'
},
body: JSON.stringify(params), // 序列化傳入參數
signal: abortController.value.signal
});
// ok字段判斷是否成功獲取到數據流
if (!response.ok) throw new Error('Network response was not ok');
if (!response.body) throw new Error('Network response was not body');
// 用來獲取一個可讀的流的讀取器(Reader)以流的方式處理響應體數據
const reader = response.body.getReader();
// 將流中的字節數據解碼為文本字符串
const textDecoder = new TextDecoder();
while (true) {
// 檢查是否已中斷
if (abortController.value.signal.aborted) break;
// done表示流是否已經完成讀取 value包含讀取到的數據塊
const { done, value } = await reader.read();
if (done) {
// 流式數據讀取完成
break;
}
// 拿到的value就是後端分段返回的數據,大多是以data:開頭的字符串
// 需要通過decode方法處理數據塊,例如轉換為文本或進行其他操作
// (replace可選)數據可能不是你想要的格式,做特殊處理(僅移除單獨的換行符,保留連續的換行符)
const chunkText = textDecoder.decode(value).replace(/(?<!\n)\n(?!\n)/g, '');
// 無數據則跳過
if (!chunkText) continue;
// 有數據則拼接,此處可以寫你自己的邏輯代碼,比如數據清洗
last.content += chunkText;
}
// 流式片段遍歷結束,停止響應
responseClose();
} catch (err) {
console.log(err);
responseClose();
}
- 通過
textDecoder.decode將流式字節數據解碼為文本字符串 - 通過
response.body.getReader();讀取流的主體 - 通過
reader.read();方法讀取流的分塊,讀出分塊後,可以做你喜歡的事
下面是一個在AI對話中的場景示例:
// 開始對話
const chatList = ref<any>([]);
const isChatting = ref(false);
const abortController = ref(); // 手動停止對話
const startChat = async (keyword: string) => {
try {
if (!keyword) return MessagePlugin.warning('請輸入內容');
// 先停止對話
await stopChat();
let last = chatList.value.at(-1);
// 參數
let params = {};
// 對話中
isChatting.value = true;
const url = 'xxxx/xxx/xxxAPI';
abortController.value = new AbortController();
// 發送請求
let response = await fetch(url, {
method: 'post',
headers: {
Authorization: '你的token',
'Content-Type': 'application/json'
},
body: JSON.stringify(params), // 序列化傳入參數
signal: abortController.value.signal // 定義手動停止接口
});
// 處理401
if (response.status == 401) return login();
// ok字段判斷是否成功獲取到數據流
if (!response.ok) throw new Error('Network response was not ok');
if (!response.body) throw new Error('Network response was not body');
// 用來獲取一個可讀的流的讀取器(Reader)以流的方式處理響應體數據
const reader = response.body.getReader();
// 將流中的字節數據解碼為文本字符串
const textDecoder = new TextDecoder();
while (true) {
// 檢查是否已中斷
if (abortController.value.signal.aborted) {
break;
}
// done表示流是否已經完成讀取 value包含讀取到的數據塊
const { done, value } = await reader.read();
if (done) {
// 流式數據讀取完成
break;
}
// 拿到的value就是後端分段返回的數據,大多是以data:開頭的字符串
// 需要通過decode方法處理數據塊,例如轉換為文本或進行其他操作
// (replace可選)數據可能不是你想要的格式,做特殊處理(僅移除單獨的換行符,保留連續的換行符)
const chunkText = textDecoder.decode(value).replace(/(?<!\n)\n(?!\n)/g, '');
// 無數據則跳過
if (!chunkText) continue;
// 有數據則拼接,此處可以寫你自己的邏輯代碼,比如數據清洗
last.content += chunkText;
// 滾動條置底-可選
scrollMax(false);
}
// 流式片段遍歷結束
responseClose();
} catch (err) {
console.log(err);
responseClose();
} finally {
}
};
// 響應結束
const responseClose = () => {
// 停止流式對話
stopChat();
};
// 手動停止函數
const stopChat = async () => {
// 手動終止fetch請求
if (abortController.value) {
abortController.value.abort();
abortController.value = null;
}
// 停止當前對話
isChatting.value = false;
// 處理最後一條數據
let lastData = chatList.value.at(-1);
lastData.loading = false;
lastData.contentEnd = true;
};
參考文檔:
Fetch API
使用流的方式處理 Fetch
TextDecoder.decode
AbortController:AbortController() 構造函數