一.fetch与axios的比较
fetch
和 axios
是两种常用的 HTTP 请求工具,在前端开发中经常使用来与后端进行通信。它们有一些重要的区别和各自的优缺点。其中流式处理在我们实现逐字输出中起到了主要作用
fetch
fetch
是现代浏览器内置的 API,用于发起网络请求。它返回的是一个 Promise
,更加现代和简洁。
优点:
-
内置于浏览器:不需要额外的库文件,减少了依赖。
-
基于 Promise:更简洁和易于链式调用。
-
流式处理:支持
ReadableStream
,方便处理大数据量或流式数据。 -
灵活性高:可以使用
Request
和Response
对象进行更多自定义操作。
缺点:
-
不自动处理 JSON:需要手动调用
response.json()
来处理 JSON 数据。 -
不支持取消请求:没有内置的取消请求机制(虽然可以使用
AbortController
进行额外处理)。 -
缺少一些常用功能:如超时设置、全局配置、拦截器等。
二. fetch的使用
使用步骤
-
发送请求:
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question: question }) });
-
使用
fetch
发送 POST 请求到指定的 URL,设置请求头Content-Type
为application/json
,并将question
作为请求体发送。
-
-
检查响应体是否支持流:
if (!response.body) { throw new Error('ReadableStream not yet supported in this browser.'); }
-
检查
response.body
是否存在。如果不存在,抛出错误,表明浏览器不支持ReadableStream
。
-
-
读取数据流:
jconst reader = response.body.getReader(); let decoder = new TextDecoder('utf-8');
-
获取读取器
response.body.getReader()
用于读取响应的流数据。 -
创建一个
TextDecoder
实例,用于将二进制数据解码为字符串。
-
-
处理数据流:
while (true) { const { done, value } = await reader.read(); if (done) break; // 如果数据流结束,退出循环 const chunkData = decoder.decode(value, { stream: true }).trim(); this.streams.push(chunkData); this.updateMessagesFromStreams(); }
-
使用
while (true)
循环持续读取数据流。 -
reader.read()
返回一个 Promise,解析后得到一个对象
{ done, value }
:
-
done
:如果为true
,表示数据流已结束,退出循环。 -
value
:当前读取的二进制数据块。
-
-
使用
decoder.decode
将二进制数据解码为字符串,并存入streams
数组。 -
每读取到一个数据块后,调用
updateMessagesFromStreams
方法来更新messages
数组。
-
-
更新 messages 数组:
updateMessagesFromStreams() { const combinedMessage = this.streams.join(''); this.messages = [{ content: combinedMessage, type: 'bot' }]; }
-
updateMessagesFromStreams
方法将streams
数组中的所有字符串连接成一个完整的字符串。 -
更新
messages
数组,将连接后的字符串作为新的消息内容。
-
总结
通过使用 fetch
进行流式请求,可以有效地逐块处理数据并实时更新前端显示。与 axios
类似,fetch
的 ReadableStream
使得处理流数据更加简洁和现代化。此方法在数据量较大或需要逐步处理和显示数据的场景下尤为适用。
三. 实际代码实现
async sendToBackend(question) {
const url = "http://localhost:5000/api/query"; // 确保这是正确的后端URL
this.streams = [];
this.sending = true;
try {
// 发送流式请求
const response = await axios({
method: 'post',
url: url,
data: { question: question },
responseType: 'stream'
});
const reader = response.data.getReader();
let decoder = new TextDecoder('utf-8');
// 读取数据流
while (true) {
const { done, value } = await reader.read();
if (done) break; // 如果数据流结束,退出循环
// 假设数据是字符串形式的,直接存入 streams 数组
const chunkData = decoder.decode(value, { stream: true }).trim(); // 假设数据是一个个汉字字符串
this.streams.push(chunkData);
// 更新 messages 数组(每获取到一个块就更新一次)
this.updateMessagesFromStreams();
}
// 标记发送完成
this.sending = false;
} catch (error) {
console.error('Error sending to backend:', error);
this.sending = false;
}
},
updateMessagesFromStreams() {
// 将 streams 数组中的所有字符串连接成一个,并存入 messages 数组
const combinedMessage = this.streams.join('');
this.messages = [{ content: combinedMessage, type: 'bot' }];
}