前端实现流式输出《后端返回Markdown格式文本,前端输出类似于打字的那种》
一、使用插件
插件名称:marked
版本:15.0.11
安装插件:npm install marked@15.0.11
作用:marked 是一个用于将 Markdown 语法转换为 HTML 的 JavaScript 库
插件2名称:dompurify
版本:3.2.5
安装插件:npm install dompurify@3.2.5
作用:DOMPurify 是一个专门用于防止 XSS(跨站脚本攻击)的快速、易用且强大的工具。
二、代码实现
<div v-html="MdHtml"></div>import { marked } from 'marked'import DOMPurify from 'dompurify'// 推荐使用fetch function Fetch({ url, method, data }) {return new Promise(async (resolve, reject) => {window.controller = new AbortController();let sig = window.controller.signal;try {var response = await fetch(url, {headers: {'Authorization': 'Bearer ' + gettoken(),'Content-Type': 'application/json'},method: method,body: JSON.stringify(data),signal:sig })if (response.status != 200) {reject(response)} else {resolve(response)}} catch (err) {reject(err)}})
}
const MdHtml = ref('')
// 发送网络请求
const API = async () => {let param = {"id": 'xxx',}let outText = '' // 用于存储所有接收到的数据块的文本。Fetch({url: `/xxx/xxx/xxx`,method: 'post',data: param}).then(async res => {// res.body 是一个可读的流const reader = res.body.getReader() // getReader 方法返回一个读取器(ReadableStreamDefaultReader),用于读取流中的数据块const dec = new TextDecoder() // 用于将二进制数据解码为文本(解码当前数据块)while (true) {/*** reader.read() 返回一个 Promise,解析为一个对象,包含 done 和 value。* done 表示流是否已经结束。* value 是当前数据块的二进制数据。* 如果 done 为 true,表示流已经结束,调用 getComparsonHistoryNet() 并退出循环*/const { done, value } = await reader.read()if (done) { // 结束后调用别的函数break}const ck = dec.decode(value, { stream: true }) // stream: true 表示这是一个流式解码,适用于部分数据。outText += ck// 解码后的文本块添加到 outText 中const outHtml = marked.parse(outText) // 使用 marked 库将 outText 转换为 HTMLMdHtml.value = DOMPurify.sanitize(outHtml) //使用 DOMPurify 库对生成的 HTML 进行安全处理,防止 XSS 攻击 (将处理后的 HTML 赋值给 MdHtml.value,通常用于更新 Vue 组件的响应式数据)};onMounted(() => {API()
})
三、为什么用 fetch 但是不用 axios
在前端实现流式输出(例如处理服务器发送的事件流,如 SSE 或者分块传输的响应),fetch API 相比 axios 更受推荐的原因主要在于其对流式数据处理的原生支持以及更灵活的控制能力。以下是具体原因:
1. 原生支持流处理
fetch 提供了对 ReadableStream 的直接支持,允许你通过 .body 属性访问响应体作为一个可读流。这意味着你可以逐块读取数据,并且可以在数据到达时立即开始处理,而不是等待整个响应完成。
fetch('your-stream-endpoint').then(response => {const reader = response.body.getReader();const decoder = new TextDecoder('utf-8');function read() {reader.read().then(({ done, value }) => {if (done) {console.log('Stream complete');return;}console.log(decoder.decode(value, { stream: true }));read(); // Continue reading});}read();});
axios 默认情况下不支持流式处理,尽管可以通过配置 responseType: ‘stream’ 来尝试获取流数据,但这通常仅限于 Node.js 环境下使用,并且对于浏览器端的支持不如 fetch 自然和直接。
四、总结
当涉及到流式数据处理时,fetch 因为其对 ReadableStream 的原生支持、更轻量的特点以及更高的灵活性,成为了更优的选择。然而,这并不意味着 axios 不好,它在很多其他场景下仍然是一个强大的 HTTP 客户端工具,尤其是在需要高级功能如请求/响应拦截、自动转换 JSON 数据等方面表现突出。选择哪个工具应基于具体的项目需求来决定。如果项目中大量使用了 axios 并且对流式处理的需求不多,可能继续使用 axios 加上适当的扩展也是可行的。但如果主要是为了处理流式数据,那么 fetch 将是更好的选择。