实现chatGpt中的数据依次展示效果(Fetch+nodeJs)

1. 首先看看效果:

流式数据获取并展示

左右两侧前端代码差异:

左右两侧是请求同一个node接口,前端使用的是 fetch

在左侧对 fetch 返回数据的处理是:response.body.getReader().read去监听response中body数据的每一次数据变化,并直接开始渲染数据
在右侧对fetch 返回的数据处理是:直接等待response.data

在视频中可以明显看出,左侧的数据处理模式可以无需等待全部数据加载完毕便开始渲染,极大提升了大数据传输的用户体验

2. 代码设计

node部分

  1. 读取要传输的文件数据
  2. 按照分片大小创建可读流数据
  3. 监听创建的可读流数据变化,每次变化时将数据写入响应给前端的 res 中
    上代码:

exports.getStreamTest = function (req, res) {
  const filePath = path.join(__dirname, 'streams') // 同级目录下有一个 streams 文件,里面放着要传输的数据
  const fileStats = fs.statSync(filePath); // 获取streams 文件的基本信息
  const fileSize = fileStats.size;
  const chunkSize = 8; // 由于我测试的文本数据大小较小,所以我将数据分片分的比较小来模拟大文件情况
  // 计算需要拆分的块数
  const numChunks = Math.ceil(fileSize / chunkSize);
    // 设置响应头,表明要进行分块传输
    res.setHeader('Content-Type', 'application/octet-stream');
    res.setHeader('Content-Length', fileSize.toString());
    res.setHeader('Content-Disposition', 'attachment; filename=file.xlsx');
    res.setHeader('Transfer-Encoding', 'chunked');

    // 使用流式 API 读取数据,并按照顺序逐步返回给前端
    let bytesRead = 0;
    let currentChunk = 0;

    const stream = fs.createReadStream(filePath, {
      highWaterMark: chunkSize,
    });

    stream.on('data', (chunk) => {
      console.log(Buffer.isBuffer(chunk)); // 这里的chunk 数据类型是 buffer
      bytesRead += chunk.length;
      currentChunk++;

      // 如果当前块不是最后一块,则在其末尾添加一个分块标记
      if (currentChunk < numChunks) {
        res.write(chunk + '\r\n');
      } else {
        res.write(chunk);
        res.end();
      }
    });

    stream.on('error', (err) => {
      console.error(err);
      res.status(500).end();
    });
}

前端部分

左侧

async getStream() {
      const _this = this
      try {
        const resData = await fetch('http://127.0.0.1:8888/noToken/largeFile/getStreamTest', {
          method: 'POST',
          body: JSON.stringify({
            'dateawaaa': '111'
          })
        })
        .then(async function (response) {
          let MAX_TIME = 2000;
          let chunks = [];
          const reader = response.body.getReader(); // 创建读取流
          let bytesRead = 0;
          async function pump() {
            if(MAX_TIME){
              MAX_TIME-- 
              const { done, value } = await reader.read();
		      // read()中有两个字段:是否已经全部传输完毕:done、变化的数据:value
              if (value) {
                bytesRead += value.length;
                const textDecoder = new TextDecoder(); // fetch获取的数据是流,这里需要文本解码
                const textStream = textDecoder.decode(value, {stream: true});
                const getStreamText = document.getElementById('getStream');
                await _this.writeStreamText(getStreamText, String.fromCharCode(...JSON.parse(`[${textStream[0]}]`))) // 数据渲染到页面
                chunks.push(value); // j将每一片数据存起来,如需要可以在{ done: true}时对全部数据进行处理
              }
              await pump();
            } else{
              return false;
            }
          }
          return await pump(); // 流数据变化时递归处理
        })
      } catch (err) {
        console.error(err)
      }
    },

视频右侧效果代码

normalData() {
      axios.post('http://127.0.0.1:8888/noToken/largeFile/getStreamTest').then(res => {
        if(res) {
          this.writeStreamText(document.getElementById('getNormal'), res.data)
        }
      });
async writeStreamText(getStreamText,str) {
      return new Promise(res => {
        setTimeout(() => { // 这里我用的文本数据太少了,接口响应很快,每个分片数据渲染加50ms的延迟让效果更明显一点
          getStreamText.innerHTML += str
          res();
        }, 50);
      })
    }

你可能感兴趣的:(chatgpt,前端,javascript,vue.js,node.js)