使用 node 和 html 进行流式读取数据的模拟

146 阅读2分钟

看了一篇讲了chatgpt的文字输出的视频,就想记录一些 流式获取数据

  1. 前端部分主要是请求一个接口
  let url = 'http://localhost:3000/flow';
  1. 进行流式数据的获取 resp.body 的 body是一个流; 流是一个抽象的概念,比方说,之前的请求方式是一口气的获取所有的,现在的这个就是慢慢的去获取,有一点数据就拿取一点数据;在流中有一个读取器 getReader
  const resp = await fetch( url,{
                method:'post',
                headers:{
                    "Content-Type":"application/json"
                }
            })
            const reader = resp.body.getReader();
  1. getReader它可以使用一个函数( read )进行一部分一部分的读; 但是因为read函数读取这一块的数据也是需要时间的,所以它返回的也是一个promsie, 其中有两个属性可以拿到 donevalue;

done 为true 表示读完了,没有了;

value 表示读出来的数据,读取出来的数据为类型化数组;

因为调用这个函数就会读出来一小块,所以我们放在循环里面; 调用 new TextDecoder()decode 方法进行编码,然后返回字符串

 const textDecoder = new TextDecoder();
            while(1){
                const { done,value } = await reader.read();
                if(done){
                    break;
                }
                // value是一个 Uint8Array 的编码格式 是数组类型表示一个 8 位无符号整型数组
                // 需要使用new TextDecoder() 的decode 进行编码 
                console.log(value);
                const txt = textDecoder.decode(value);
                // console.log(txt);
                str += txt
                console.log(str);
            }
        

以下是前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>流式接口</title>
</head>
<body>
    
    <script>
        let url = 'http://localhost:3000/flow'
        let str = '';
        async function gertResponse(){
            const resp = await fetch( url,{
                method:'post',
                headers:{
                    "Content-Type":"application/json"
                }
               
            })
            const reader = resp.body.getReader();
            const textDecoder = new TextDecoder();
            while(1){
                const { done,value } = await reader.read();
                if(done){
                    break;
                }
                // value是一个 Uint8Array 的编码格式 是数组类型表示一个 8 位无符号整型数组
                // 需要使用new TextDecoder() 的decode 进行编码 
                console.log(value);
                const txt = textDecoder.decode(value);
                // console.log(txt);
                str += txt
                console.log(str);
            }
        }

        gertResponse();
    </script>
</body>
</html>

以下是后端代码

const Koa = require('koa')
const app = new Koa()
const { PassThrough } = require("stream")
app.use(async (ctx, next) => {
    ctx.set('Access-Control-Allow-Origin', '*')
    ctx.set('Access-Control-Allow-Headers', 'content-type')
    ctx.set('Access-Control-Allow-Methods', 'OPTIONS,GET,PUT,POST,DELETE')
    await next()
})
const Router = require('koa-router');
const router = new Router();

app.use(async (ctx, next) => {
    ctx.set('Access-Control-Allow-Origin', '*')
    ctx.set('Access-Control-Allow-Headers', 'content-type')
    ctx.set('Access-Control-Allow-Methods', 'OPTIONS,GET,PUT,POST,DELETE')
    await next()
    // 允许所有跨域
    if (ctx.request.method === 'OPTIONS') {
        console.log('跨域请求')
        ctx.response.status = 200
        ctx.response.message = 'OK'
    }
})

// 启动路由
app.use(router.routes()).use(router.allowedMethods())

router.post('/flow', async (ctx,next) => {
    ctx.request.socket.setTimeout(0);
    ctx.req.socket.setNoDelay(true);
    ctx.req.socket.setKeepAlive(true);

    const stream = new PassThrough();
    
    let str = '您好,请问这个人类是谁啊?为什么我不认识'.split("")
    ctx.status = 200;
    ctx.body = stream;
    let n = 0;
    setInterval(() => {
        stream.write( str[n]);
        n++;
        if(n > str.length - 1){
            n = 0;
        }
    }, 1000);

})

app.listen(3000)