likes
comments
collection
share

【axios】你的进度条准确吗

作者站长头像
站长
· 阅读数 47

1、axios监听进度

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

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文件中,可以看到这么一段代码

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的实现:

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、不准确的进度条

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

  • 计算出来的进度与实际上传进度不符合

【axios】你的进度条准确吗

  • 可以看出:进度条已经走完,但是接口一直在pending

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

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

原因

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

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

【axios】你的进度条准确吗

3、模拟进度条

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

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

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

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库也是这么做的,我们平时使用它的姿势是这样的:

NProgress.start()  // 开启进度条

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

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

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

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

【axios】你的进度条准确吗

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

参考:

  1. github.com/axios/axios
  2. github.com/rstacruz/np…
转载自:https://juejin.cn/post/7284885060338794515
评论
请登录