【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...
相关推荐
为美好的生活献上中指1 小时前
java每日精进 5.11【WebSocket】
java·javascript·css·网络·sql·websocket·网络协议
拖孩3 小时前
【Nova UI】十五、打造组件库之滚动条组件(上):滚动条组件的起步与进阶
前端·javascript·css·vue.js·ui组件库
苹果电脑的鑫鑫3 小时前
element中表格文字剧中可以使用的属性
javascript·vue.js·elementui
一丝晨光4 小时前
数值溢出保护?数值溢出应该是多少?Swift如何让整数计算溢出不抛出异常?类型最大值和最小值?
java·javascript·c++·rust·go·c·swift
Wannaer4 小时前
从 Vue3 回望 Vue2:响应式的内核革命
前端·javascript·vue.js
懒羊羊我小弟4 小时前
手写符合Promise/A+规范的Promise类
前端·javascript
赵大仁4 小时前
React vs Vue:点击外部事件处理的对比与实现
javascript·vue.js·react.js
肥肥呀呀呀6 小时前
在Flutter上如何实现按钮的拖拽效果
前端·javascript·flutter
付朝鲜6 小时前
用自写的jQuery库+Ajax实现了省市联动
java·前端·javascript·ajax·jquery
coderYYY7 小时前
多个el-form-item两列布局排齐且el-select/el-input组件宽度撑满
前端·javascript·vue.js·elementui·前端框架