【axios】你的进度条准确吗

1、axios监听进度

上传和下载操作在前端中是非常常见的,当我们想知道上传或下载的进度时也不难,axios已经实现了监听进度的方法

JavaScript 复制代码
import axios from 'axios'

// 上传请求
axios.post('/api/v1/upload', {
    data: xxx
  },
  {
    // onUploadProgress回调可以获取进度
    onUploadProgress(e) {
      const complete = e.loaded / e.total * 100
    }
	}
)

// 或者
axios({
  method: 'POST',
  data: {
    data: xxx
  },
  onUploadProgress(e) {
    const complete = e.loaded / e.total * 100
  }
})

翻一下axios的源码,看看它是如何实现的

/lib/adapters/xhr.js文件中,可以看到这么一段代码

JavaScript 复制代码
let request = new XMLHttpRequest()

// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
  request.upload.addEventListener('progress', progressEventReducer(config.onUploadProgress));
}

axios是基于XMLHttpRequest来实现的

其中,config就是我们传给的axios的参数,如果是上传操作并且有传递onUploadProgress函数的话

就监听XMLHttpRequestprogress事件,然后周期性地触发回调函数progressEventReducer

再看一下progressEventReducer的实现:

ini 复制代码
function progressEventReducer(listener, isDownloadStream) {
  return e => {
    const loaded = e.loaded;
    const total = e.lengthComputable ? e.total : undefined;
    ...

    const data = {
      loaded,
      total,
      progress: total ? (loaded / total) : undefined,
      ...
    };

    data[isDownloadStream ? 'download' : 'upload'] = true;  // 区别上传或是下载

    listener(data);
  };
}

progressEventReducer中,就会获取

  • loaded:已上传的buffer数据流
  • total:总的buffer数据流

最终会将数据传给listener,即一开始传给axiosonUploadProgress回调,所以我们可以通过e.loaded / e.total来获取进度

2、不准确的进度条

获取进度条很简单,但是在实际使用中还是碰到了问题

  • 计算出来的进度与实际上传进度不符合
  • 可以看出:进度条已经走完,但是接口一直在pending

这个问题还是比较严重的,明明显示完成了上传,但是文件就是没有出来

而且文件越大,差异就越明显

原因

造成这个原因,其实跟TCP协议发送数据的方式有关

  • 在客户端,会有一个send buffer,即数据缓存区
  • 这个buffer缓存区存储的就是等待发送的数据,tcp协议层会在适当的时候选取一部分数据发送出去
  • 当我们上传文件时,数据会被不断地 write到这个缓存区;每次写入时 就会被侦听到,然后调用一次 onUploadProgresse.loaded其实表示的就是写入到buffer缓存区的数据
  • 但是此时的数据可能还没有被发送出去,仍然在缓存区中放着;加上发送数据也需要时间
  • 等到数据全部到达Server的时候,才会执行response回调,这时候上传操作才算完成;但客户端那边早就显示发送完毕了,时差也就出来了

3、模拟进度条

如果解决这个问题呢?有一个比较保守的做法:用一个亦真亦假的进度条

  • 在进度达到一定值的时候,开始人为干涉;每次让它走一点,给人一种一直在上传的感觉,直到上传完成,赋值为100即可

这里就使用vue3的方式实现一下:

JavaScript 复制代码
import { ref } from 'vue'

/**
 * 模拟进度条
 * @returns
 */
export const useProgress = (name: string) => {
  const progress = ref<number>(0)
  const timer = ref<number | null>(null)

  const onUploadProgress = (e: ProgressEvent) => {
    const complete = (e.loaded / e.total) * 100

    // 超过80 开始干涉
    if (complete >= 80) {
      if (timer.value) return
      timer.value = window.setInterval(() => {
        progress.value += (100 - progress.value) * 0.2  // 每次增加剩下的20%
        
        // 超过99 不再变化(此时也接近上传完成了)
        if (progress.value > 99) {
          timer.value && window.clearInterval(timer.value)
        }, 1000)
      } else {
      // 在80之前都按照axios计算出的进度来显示
      progress.value = complete
    }
	}
  
  return {
    progress,
    onUploadProgress
  }
}
  • 可以在请求完成的时候,将progress设置为100即可
  • 具体的阈值可以根据情况自行设置

其实,大名鼎鼎的NProgress库也是这么做的,我们平时使用它的姿势是这样的:

JavaScript 复制代码
NProgress.start()  // 开启进度条

NProgress.done()  // 进度条结束

可是,又没有监听接口,仅仅只是开启进度条,它怎么能预测我们的进度呢?

答案是:它也是模拟进度条

在其源码中,有这么一段代码:

  • 当我们没有传amount的时候,会自动根据当前进度条的status调整每次的进度amount
  • 当进度staus超过0.994的时候,就会一直停在0.994这个状态
  • 直到我们执行NProgress.done()时,会直接调用NProgress.set(1),进度条走完

参考:

  1. github.com/axios/axios
  2. github.com/rstacruz/np...
相关推荐
空中海4 小时前
01 React Native 基础、核心组件与布局体系
javascript·react native·react.js
前端之虎陈随易6 小时前
2年没用Nodejs了,Bun很香
linux·前端·javascript·vue.js·typescript
好运的阿财7 小时前
OpenClaw工具拆解之host_workspace_write+host_workspace_edit
前端·javascript·人工智能·机器学习·ai编程·openclaw·openclaw工具
XiYang-DING7 小时前
JavaScript
开发语言·javascript·ecmascript
空中海8 小时前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海9 小时前
02 状态、Hooks、副作用与数据流
开发语言·javascript·ecmascript
空中海9 小时前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
杨超凡10 小时前
豆包收费了?我特么自己用“意念”搓了一个!
javascript
threelab11 小时前
Three.js 咖啡杯烟雾效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能