编程技巧:VUE 实现接口返回数据的流式处理

一、写在前面

ChatGPT 的问答响应界面相信大家都见过,内容是一点一点追加式的显示。不是等好了一起发给你,然后一次性展示出来。这种效果和我们平常开发的展示渲染模式有点区别。可能有的同学会说,前端拿到报文后,我们做成这样的效果不就行行了,有什么难的。

这话看起来很对,但其实不那么对。试想一下,如果一个问答内容响应体很大,几十上百兆,等报文传输完了,我们再显示,中间界面等待的时间会很长,体验其实是很差。

那有什么办法能加速在大报文的场景下,能加速前端界面渲染效果呢?

二、传统式交互

bash 复制代码
const url ='http://ip:port/path';
async function getResponse(){
    const resp = await fetch(url,{
        method : 'GET',
    });
    console.log('123');
    const data = await resp.text();
    console.log(data);
}
getResponse();

直接从HttpResponseBody中直接一次性获取服务端的响应内容是传统式编程开发。

三、流式传输交互

我们都知道网络传输其实并不是一次性发送所有报文,其实是一部分一部分的传输。也就是说服务端网络是一部分一部分,客户端网络也是一部分一部分接收的,然后拼在一起拿出来的。

那我们有没有办法传输一部分的时候我们就直接一部分的一部分,不用等全部传完我们再拿呢?答案是有的。

bash 复制代码
const url ='http://ip:port/path';
async function getResponse(){
    const resp = await fetch(url,{
        method : 'GET',
    });
    // 获取 response 读取器
    const reader = resp.body.getReader();
    // 创建一个文本解码器
    const decoder = new TextDecoder();
    while (1){
        // 一块一块读
        // done 代表读完了没有,value代表此次读的内容是啥
        const {done,value} = await reader.read();
        const text = decoder.decode(value)
        console.log(done);
        console.log(text)
        if(done){
            break;
        }
    }
}

如此我们就能提前拿到报文,不需要等报文都传输完再获取。这样加快了前段渲染出数据的速度,而且也达到了ChatGPT那种一点一点追加式显示的效果。

四、监控响应进度

前端实现 Ajax 无非两种做法,一种是XHR,这个API比较老,也比较传统,也是至今用的最广的一种。一种是Fetch,是一种比较新的API。这些都是浏览器的原生能力,我们要知道,原生能力都做不到的话,第三方库也是做不到的。axios 是基于 XHR实现的,umi-request 是基于 Fetch 实现的。原生是能力的边界,第三方库也只能在边界里面玩,这是定数。

上图是这两种 API 原生能力支持的对比说明。

我们现在要实现监控响应进度,那么XHR和Fetch都是能实现的。

如果要做请求进度监控,比如上传进度监控,监控客户端的报文是否都给了服务端,无可厚非,只能选择XHR或者axios。

五、Fetch 监控响应进度实现

bash 复制代码
const url ='http://ip:port/path';
async function getResponse(){
    const resp = await fetch(url,{
        method : 'GET',
    });
    const total = +resp.headers.get('content-length');
    let body = '';
    let loaded = 0;
    // 获取 response 读取器
    const reader = resp.body.getReader();
    // 创建一个文本解码器
    const decoder = new TextDecoder();
    console.log(total);
    while (1){
        // 一块一块读
        // done 代表读完了没有,value代表此次读的内容是啥
        const {done,value} = await reader.read();
        if(done){
            break;
        }
        loaded += value.length;
        const text = decoder.decode(value)
        body += text
        console.log(loaded);
        // console.log(body);
        // console.log(done);
        // console.log(text)
    }
}
getResponse();

上面方法是通过读取response的header头中content-length 属性获取报文总大小,然后计算每次获取的量,这样就达到了监控响应进度的目的。

六、XHR 监控响应进度实现

bash 复制代码
const url ='http://ip:port/path';
async function getResponse(){
    const xhr = new XMLHttpRequest();
    // 监听读取内容状态变更事件
    xhr.addEventListener('readystatechange',()=>{
        // 当都读取状态为结束时
        if(xhr.readyState === xhr.DONE){
            // 完整报文
            console.log(xhr.responseText);
        }
    });
    // 监听进度事件
    xhr.addEventListener('progress',(e)=>{
        console.log(e);
    });
    xhr.open('GET',url);
    xhr.send();
}
getResponse();

XHR是事件类型的 API 风格,是通过监听 xhr 不同的事件来达到获取进度的目的。

七、响应进度监控的特例

上面不论是Fetch API 还是 XHR API 都不是百分百可行的。在开启服务端 nginx 的 gzip 压缩时,两种获取进度的能力将全部失效。无法在传输的过程中获取到报文的总大小或者 response 的 header 头中无 content-length 属性。

在Http 1.0及之前版本中,content-length字段可有可无。

在http1.1及之后版本,如果是 keep alive,如果存在Transfer-Encoding(重点是chunked),则在header中不能有Content-Length,有也会被忽视。 content-length 和 Transfer-Encoding:chunked 必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无。

在nginx开启gzip压缩时,则 response 的 header 头中存在的是 Transfer-Encoding:chunked,则不会存在 content-length。

所以在这种情况下,无法实现报文相应进度监控,但是能持续获取到报文的大小已传输的大小

相关推荐
天道kabuto1 小时前
前端每日知识点:React 与 Vue Diff 算法对比 · 完整总结
vue.js·react.js·前端框架
阳火锅1 小时前
2025,记住这一年!它是古法编程的最后一年。
前端·javascript·面试
Bs_MoneyMagnet1 小时前
基于springboot+vue的医院陪诊服务预约平台的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring
Leo.yuan2 小时前
Flink + Kafka + Doris 之外的另一条路:FineDataLink 一体化实时数仓方案
开发语言·javascript·ecmascript
只睡四小时2 小时前
端侧推理实战:face-api.js 零后端人脸识别踩坑记
开发语言·javascript·ecmascript
李少兄2 小时前
JavaScript 对象完全指南
开发语言·javascript·ecmascript
何何____3 小时前
web方向js考核题解
开发语言·前端·javascript
liangshanbo12153 小时前
面试题终极整理:手写 Promise.all / Promise.allSettled
前端·javascript·promise