前端实现流式输出《后端返回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(跨站脚本攻击)的快速、易用且强大的工具。

二、代码实现

javascript 复制代码
  <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 转换为 HTML
        MdHtml.value = DOMPurify.sanitize(outHtml) //使用 DOMPurify 库对生成的 HTML 进行安全处理,防止 XSS 攻击 (将处理后的 HTML 赋值给 MdHtml.value,通常用于更新 Vue 组件的响应式数据)
  };

onMounted(() => {
    API()
})

三、为什么用 fetch 但是不用 axios

在前端实现流式输出(例如处理服务器发送的事件流,如 SSE 或者分块传输的响应),fetch API 相比 axios 更受推荐的原因主要在于其对流式数据处理的原生支持以及更灵活的控制能力。以下是具体原因:

1. 原生支持流处理

fetch 提供了对 ReadableStream 的直接支持,允许你通过 .body 属性访问响应体作为一个可读流。这意味着你可以逐块读取数据,并且可以在数据到达时立即开始处理,而不是等待整个响应完成。

javascript 复制代码
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 将是更好的选择。

相关推荐
碳基修炼1 分钟前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
步行cgn3 分钟前
Spring p 命名空间注入详解
java·前端·spring
何何____10 分钟前
Vue 生命周期详解
前端·javascript
江米小枣tonylua30 分钟前
TypeScript 全面の拥抱 !Prisma 8 + Electron 升级实战
前端
闪耀之光M781 小时前
npm命令解析
前端
咸鱼老弟1 小时前
AI Agent 的"自主性悖论"——为什么给它的自由度越大,越要配一套更硬的护栏
前端·人工智能
默_笙2 小时前
💫 闭包是个背包:拆解小米前端面试题里的三道"闭包陷阱"
前端·javascript·面试
天若有情6732 小时前
粒子星空背景单页HTML模板|炫酷动态星空特效网页(纯前端)
前端·html·css动画·网页特效·粒子星空·静态网页模版
工业涂料百问2 小时前
【趋势前沿】系列(三)工业涂料国产替代的三个台阶:航空、海工、电子的突破路径与认证壁垒
前端
前端snow2 小时前
ai agent --- agentic RAG
前端