返回
创建于
状态公开

fetch 获取上传进度

https://stackoverflow.com/questions/35711724/upload-progress-indicators-for-fetch

Demo: File Upload Progress Demo - Using Fetch Duplex Streaming

typescript
1/**
2 * 表示上传进度事件的类
3 */
4class FetchProgressEvent {
5  constructor(
6    public lengthComputable: boolean,
7    public loaded: number,
8    public total: number,
9  ) {}
10}
11
12/**
13 * 计算流的总大小
14 */
15export async function getStreamSize(stream: ReadableStream): Promise<number> {
16  const reader = stream.getReader()
17  let size = 0
18
19  while (true) {
20    const { done, value } = await reader.read()
21    if (done)
22      break
23    size += value.length
24  }
25
26  return size
27}
28
29/**
30 * 为请求添加进度跟踪功能
31 * @param request 原始请求
32 * @param onProgress 进度回调
33 */
34async function trackRequestProgress(
35  request: Request,
36  onProgress?: (evt: FetchProgressEvent) => void,
37): Promise<Request> {
38  if (!onProgress)
39    return request
40
41  // 检查浏览器支持
42  const supportsStreaming = (() => {
43    let isDuplex = false
44    const test = new Request(request.url, {
45      body: new ReadableStream(),
46      method: 'POST',
47      get duplex() {
48        isDuplex = true
49        return 'half' as const
50      },
51    })
52    return isDuplex && !test.headers.has('Content-Type')
53  })()
54
55  if (!request.body || !supportsStreaming)
56    return request
57
58  const [stream1, stream2] = request.body.tee()
59  const totalSize = await getStreamSize(stream1)
60  let loaded = 0
61
62  const progress = new TransformStream({
63    start: () => {
64      onProgress(new FetchProgressEvent(true, 0, totalSize))
65    },
66    transform: (chunk, controller) => {
67      controller.enqueue(chunk)
68      loaded += chunk.byteLength
69      onProgress(new FetchProgressEvent(true, loaded, totalSize))
70    },
71    flush: () => {
72      onProgress(new FetchProgressEvent(true, totalSize, totalSize))
73    },
74  })
75
76  return new Request(request.url, {
77    method: 'POST',
78    headers: request.headers,
79    body: stream2.pipeThrough(progress),
80    duplex: 'half',
81  })
82}
83
84export {
85  FetchProgressEvent,
86  trackRequestProgress,
87}

使用 XMLHttpRequestaxios

如果需要更简单的方式监控 FormData 的上传进度,可以直接使用 XMLHttpRequestaxios。以下是 axios 的实现:

javascript
1function uploadWithProgress(url, file) {
2    const formData = new FormData();
3    formData.append('file', file);
4
5    axios.post(url, formData, {
6        headers: {
7            'Content-Type': 'multipart/form-data',
8        },
9        onUploadProgress: (progressEvent) => {
10            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
11            console.log(`Upload Progress: ${percentCompleted}%`);
12        },
13    })
14    .then(response => {
15        console.log('Upload completed successfully!', response.data);
16    })
17    .catch(error => {
18        console.error('Upload failed:', error);
19    });
20}
21
22const fileInput = document.querySelector('#fileInput');
23fileInput.addEventListener('change', () => {
24    const file = fileInput.files[0];
25    if (file) {
26        uploadWithProgress('https://example.com/upload', file);
27    }
28});

避免了手动处理流和进度的复杂逻辑,更加简洁易用,适用于大多数场景。

Fetch上传进度跟踪