如何用Promise式(非事件发射器)方案实现前端上传任务流式处理?
基于异步生成器的前端文件上传+任务处理方案
需求说明
需要实现一个前端函数完成以下全流程,且通过迭代器输出状态以更新UI:
- 上传文件并实时返回进度百分比
- 创建任务后输出「任务已启动」状态
- 轮询任务状态直至完成
- 返回转换后的文件
UI需依次展示:上传中(带进度圈)、排队中、处理中、完成状态
用户希望采用**基于Promise的异步生成器(非事件发射器)**实现,目标通过迭代器遍历各阶段状态,以下是修正后的可行方案及原伪代码的问题分析。
原伪代码的核心问题
给出的代码存在几个关键错误:
upload是普通生成器,但内部XHR回调函数(onprogress/onload)的yield无法作用于外部生成器——回调属于独立作用域,不能直接使用生成器的yieldperformUploadJob中yield await upload(getFile())写法错误:普通生成器不能用await,需包装为异步生成器或Promise- 缺少轮询所需的
wait工具函数实现
正确的异步生成器实现方案
我们将XHR异步事件包装为可迭代的异步生成器,让进度状态能通过yield正确输出,同时串联完整流程:
完整代码实现
// 辅助工具:延迟等待函数 function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 异步生成器:文件上传+实时进度输出 async function* upload(file) { return new Promise((resolve, reject) => { const formData = new FormData(); formData.append('file', file); const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload', true); // 实时上传进度回调 xhr.upload.onprogress = (e) => { if (e.lengthComputable) { const percentComplete = Math.ceil((e.loaded / e.total) * 100); // 输出上传进度状态 this.yield({ type: 'uploading', percentComplete }); } }; // 上传完成处理 xhr.onload = () => { if (xhr.status === 200) { const response = JSON.parse(xhr.responseText); // 输出上传完成状态 this.yield({ type: 'uploaded', percentComplete: 100 }); // 返回后端生成的任务ID resolve(response.workId); } else { reject(new Error(`上传失败,状态码:${xhr.status}`)); } }; // 网络错误处理 xhr.onerror = () => reject(new Error('上传请求失败,请检查网络')); xhr.send(formData); }); } // 异步生成器:任务状态轮询 async function* pollForJobComplete(workId) { while (true) { try { const res = await fetch(`/work/${workId}`); const jobStatus = await res.json(); switch (jobStatus.status) { case 'queued': yield { type: 'queued' }; break; case 'processing': yield { type: 'processing' }; break; case 'complete': yield { type: 'result', output: jobStatus.output }; return; // 任务完成,终止轮询 } } catch (err) { yield { type: 'error', message: err.message }; throw err; } await wait(2000); } } // 主异步生成器:串联完整流程 async function* performUploadJob(file) { try { // 处理上传流程,遍历进度状态 const uploadGenerator = upload(file); let workId; for await (const status of uploadGenerator) { yield status; // 上传完成后获取任务ID if (status.type === 'uploaded') { workId = await uploadGenerator.next().value; } } // 输出任务启动状态 yield { type: 'taskStarted', message: '任务已启动' }; // 复用轮询生成器的状态输出 yield* pollForJobComplete(workId); } catch (err) { yield { type: 'error', message: err.message }; } }
正确调用方式
由于是异步生成器,必须使用for await...of遍历:
// 假设已通过文件选择器获取file对象 const file = document.querySelector('#fileInput').files[0]; async function handleUploadJob() { const jobIterator = performUploadJob(file); for await (const status of jobIterator) { switch (status.type) { case 'uploading': // 更新上传进度UI updateProgressCircle(status.percentComplete); console.log(`上传中:${status.percentComplete}%`); break; case 'uploaded': // 切换到排队状态UI showStatus('排队中'); break; case 'taskStarted': console.log(status.message); break; case 'queued': showStatus('排队中'); break; case 'processing': showStatus('处理中'); break; case 'result': showStatus('完成'); // 处理转换后的文件 handleOutputFile(status.output); break; case 'error': showError(status.message); break; } } } // 启动任务 handleUploadJob();
方案可行性验证
这种基于**异步生成器(Async Generator)**的方案完全可行,优势在于:
- 用线性的迭代写法处理异步流程,代码逻辑更直观、易维护
- 所有状态通过统一的
yield输出,UI层只需一个循环即可处理所有更新,无需绑定多个事件监听 - 天然支持错误捕获,可在生成器内部或迭代时统一处理异常
内容的提问来源于stack exchange,提问作者Lance Pollard
相关产品推荐
相关产品推荐

