Web Worker中XHR文件上传进度同步至页面进度条的实现方案咨询
需求完全可行,无需放弃Web Worker
核心思路是利用Web Worker与主线程的消息通信机制传递上传进度数据,由主线程负责更新DOM中的进度条。每个上传任务对应独立Worker+进度条的设计完全可以实现,以下是具体实现方案:
关键实现步骤
给上传任务分配唯一标识
主线程在启动上传时,给每个任务生成唯一标识(如文件名+时间戳、任务索引等),并将标识与文件、上传地址一起传给Worker,确保主线程能区分不同任务对应的进度条。Worker端传递进度与状态
在Worker的上传进度事件中,计算进度百分比后,通过postMessage将进度数据+任务标识发送回主线程;上传完成/失败的状态也通过消息同步给主线程。主线程接收消息更新UI
主线程监听Worker的message事件,根据收到的任务标识找到对应的进度条DOM元素,更新其显示状态。
修改后的Worker代码
onmessage = e => { // 接收主线程传来的任务标识、文件、上传地址 const { taskId, file, url } = e.data; const xhr = new XMLHttpRequest(); // 监听上传进度(xhr.upload的progress才是上传进度,xhr自身的progress对应下载进度) if (xhr.upload) { xhr.upload.onprogress = function(e) { if (e.lengthComputable) { const done = e.loaded; const total = e.total; const progress = Math.floor((done / total) * 100); // 发送进度数据及任务标识回主线程 postMessage({ type: 'progress', taskId, progress, done, total }); } }; } // 监听上传完成/失败状态 xhr.onreadystatechange = function() { if (this.readyState === 4) { if (this.status >= 200 && this.status < 300) { postMessage({ type: 'complete', taskId, response: this.responseText }); } else { postMessage({ type: 'error', taskId, status: this.status, message: this.statusText }); } } }; xhr.open('POST', url, true); // 不要手动设置Content-Type为multipart/form-data,FormData会自动生成包含boundary的正确请求头 const formData = new FormData(); formData.append("thefile", file); xhr.send(formData); };
主线程示例代码
// 启动单个文件上传 function startUpload(file, taskId) { const worker = new Worker('upload-worker.js'); // 找到当前任务对应的进度条元素 const progressBar = document.getElementById(`progress-bar-${taskId}`); const progressText = document.getElementById(`progress-text-${taskId}`); // 监听Worker消息 worker.onmessage = function(e) { const { type, taskId, progress, done, total } = e.data; switch(type) { case 'progress': progressBar.style.width = `${progress}%`; progressText.textContent = `${progress}% (${(done/1024/1024).toFixed(2)}MB/${(total/1024/1024).toFixed(2)}MB)`; break; case 'complete': progressText.textContent = '上传完成'; worker.terminate(); // 任务完成后终止Worker break; case 'error': progressText.textContent = `上传失败:${e.data.message}`; worker.terminate(); break; } }; // 向Worker发送任务数据 worker.postMessage({ taskId, file, url: '/your-upload-api-endpoint' }); } // 处理文件选择事件,批量启动上传 document.getElementById('file-input').addEventListener('change', function(e) { const files = e.target.files; for (let i = 0; i < files.length; i++) { const file = files[i]; const taskId = `upload-${i}-${Date.now()}`; // 生成唯一任务标识 // 动态创建进度条DOM(也可提前在页面中预设) const progressContainer = document.createElement('div'); progressContainer.innerHTML = ` <div class="file-name">${file.name}</div> <div class="progress-container"> <div id="progress-bar-${taskId}" class="progress-bar" style="width: 0%; height: 8px; background: #2196F3; border-radius: 4px;"></div> </div> <span id="progress-text-${taskId}" class="progress-text">0%</span> `; document.body.appendChild(progressContainer); // 启动上传 startUpload(file, taskId); } });
注意事项
- 浏览器对同源Worker实例数量有一定限制(通常6-8个,与同源请求限制一致),若需上传大量文件,可考虑实现Worker池复用Worker处理多个任务,只需确保每个任务仍携带唯一标识即可。
- 必须判断
e.lengthComputable,确保服务器返回了Content-Length响应头,否则无法计算准确进度。 - 不要手动设置
Content-Type: multipart/form-data,手动设置会丢失FormData自动生成的boundary参数,导致服务器无法正确解析表单数据。
内容的提问来源于stack exchange,提问作者machupiccu
相关产品推荐
相关产品推荐

