The front end uses ReadableStream.getReader to handle streaming rendering


foreword

Requirement: Let the articles returned by the interface be rendered segment by segment according to the request, and at the same time, you can click the "Stop Generation" button to stop the request.
Experience: Use pure css, vue-typed-js plug-in, and ReadableStream. Although the first two are not perfect, this way of troubleshooting and solving problems step by step is more important in my opinion. If you urgently need to judge whether it is effective, you can also go directly to the third step.
The data returned by the interface: 由于是一段一段渲染,所以是数组套对象,如下, the complete sentence is "I have longed for it with passion, and I have experienced countless disappointments and frustrations, but I finally came here."

[
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"我"
    },
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"曾满怀激情地"
    },
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"憧憬过,也"
    },
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"曾经历了无数次失望"
    },
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"和沮丧,但最终还是"
    },
    {
    
    
        "header":{
    
    
            "code":"200"
        },
        "chunk":"来到了这里。"
    }
]

1. Pure css

Due to the large amount of content, I will only talk about the ideas here, and the specific content will provide a link to the original text for viewing.
Wrap all content with p tags, which are hidden by default. Then add animation, the first 0.5s display, the second 1s display, the third 1.5s display...and so on.
Disadvantages: You need to manually add styles to n p tags, and only if the content is fixed, if the returned data is different, it cannot be used. Of course, the title of the blog is also clearly written, allowing the text to appear or display line by line.

Reference article:
css animation makes text appear line by line: https://www.5axxw.com/questions/simple/dkog6u
css animation makes text gradually appear line by line: https://www.5axxw.com/questions/simple/aawr87
https://www.5axxw.com/questions/simple/vy1p34: https://www.5axxw.com/questions/simple/vy1p34

Two, vue-typed-js plugin

Official website address: https://github.com/Orlandster/vue-typed-js

1. Install

npm install --save vue-typed-js

2. Register

import Vue from 'vue'
import VueTypedJs from 'vue-typed-js'
 
Vue.use(VueTypedJs)

3. use

<vue-typed-js
   :startDelay="1000"
   :cursorChar="'|'"
   :strings="[
     '<span>我</span>',
     '<span>曾满怀激情地</span>',
     '<span>憧憬过,也曾</span>',
     '<span>经历了无数次失望和沮丧,</span>',
     '<span>但最终还是</span>',
     '<span>来到了这里。</span>',
   ]"
   :contentType="'html'"
 >
 </vue-typed-js>

Field details:
startDelay Input delay time, unit ms
cursorChar Set what the cursor looks like
strings An array, which contains the data to be rendered
contentType Is the input text or html

Summarize

There is no pitfall, just install, register and use it directly. But there is one serious problem I can't solve.strings这个渲染的数组,如果你在vue中watch监听这个数据,每次改变的话,你会发现有旧值和新值,但如果是接口返回的数据,就只会返回数组的第一个对象的值,后面的不会返回拼接数据了,也就是strings在vue-typed-js组件中只会触发一次,

三、ReadableStream

ReadableStream MDN official website introduction: https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream

1. What is ReadableStream?

The ReadableStream interface in the Stream API represents a readable stream of byte data. The Fetch API provides a specific ReadableStream object through the property body (en-US) of the Response.

2. What does ReadableStream do?

My understanding: It is to return the content of the interface to the base64 bytes, then create a reader through getReader(), and lock the stream. Only after the current reader releases the stream, other readers can use it. The cancel() method returns a Promise that will be fulfilled when the stream is cancelled. cancel is used to completely end a stream when any data from it is no longer needed (even if there are still queued data blocks). This data is lost after cancel is called, and the stream is no longer readable.

3. How to use ReadableStream

(1) You can open the following code by setting the cross-domain Google browser

If you have not set up a cross-domain Google browser, please check my blog [Google (Chrome) browser settings cross-domain] to create a cross-domain browser: https://blog.csdn.net/weixin_44784401 /article/details/131111077

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script>
  fetch("https://www.example.org")
  .then((response) => response.body)
  .then((rb) => {
      
      
    const reader = rb.getReader();
    return new ReadableStream({
      
      
      start(controller) {
      
      
        function push() {
      
      
          reader.read().then(({
       
        done, value }) => {
      
      
            if (done) {
      
      
              console.log("done111", done);
              controller.close();
              return;
            }
            controller.enqueue(value);
            console.log("done222", done, value);
            push();
          });
        }
        push();
      },
    });
  })
  
  </script>
</body>
</html>

(2) The effect
insert image description here
can be verified by the above figure: the value in reader.read().then(({ done, value }) indicates the byte data stream readable by ReadableStream. (3) Stream
rendering data combined with the actual situation Pseudocode, mine is a vue2 project

// 注意:以下代码是伪代码,需要你结合自身需求去修改
let _this = this; // 存储this
  let data = '';
  fetch("https://www.example.org", {
    
    
    method: 'POST',
    body: JSON.stringify(data)
  })
  .then((response) => {
    
    
      const reader = response.body.getReader(); // 创建一个读取器并将流锁定于其上。一旦流被锁定,其他读取器将不能读取它,直到它被释放
      const decoder = new TextDecoder(); // TextDecoder 接口表示一个文本解码器,一个解码器只支持一种特定文本编码,例如 UTF-8、ISO-8859-2、KOI8-R、GBK,等等。解码器将字节流作为输入,并提供码位流作为输出。
      console.log('000', response.body, decoder);
      // 按顺序读取每个分块,并传递给 UI,当整个流被读取完毕后,从递归方法中退出,并在 UI 的另一部分输出整个流。
      function read() {
    
    
        // read() 返回了一个 promise 
        // done  - 当 stream 传完所有数据时则变成 true
        // value - 数据片段。当 done 为 true 时始终为 undefined
        return reader.read().then(({
     
      done, value }) => {
    
    
          if (done) {
    
    
            return;
          }
          let chunkData = ""; // 设置一个存储当前数组对象中数据的字符串
          let chunkJson = JSON.parse(decoder.decode(value)); // decoder.decode(编码)返回一个字符串,其中包含使用特定 TextDecoder 对象的方法解码的文本。(就是将原本变成字节流后把它变成文本)
          // 如果code不是200,说明请求失败,后面不执行
          if (chunkJson.header.code != "200") {
    
    
            return;
          }
          chunkData = JSON.parse(decoder.decode(value)).chunk;
          _this.render.contentText += chunkData; // 请求成功拼接字符串
          // reader.cancel()是reader停止请求的方法,停止请求后就不请求了(有一个停止生成的按钮,点击后就停止请求)
          // render.loading 是判断是否正在请求的loading,以此来判断是否继续递归请求后面的数据
          return _this.render.loading ? read() : reader.cancel();
        });
      }
      return read();
    })

ReadableStream reference link
(1) MDN ReadableStream: https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream
(2) MDN ReadableStream getReader() method: https://developer.mozilla.org/ zh-CN/docs/Web/API/ReadableStream/getReader
(3) MDN ReadableStream cancel() method: https://developer.mozilla.org/zh-CN/docs/Web/API/ReadableStream/cancel
(4) MDN TextDecoder: https://developer.mozilla.org/zh-CN/docs/Web/API/TextDecoder

Guess you like

Origin blog.csdn.net/weixin_44784401/article/details/132206265