JavaScript Promise执行乱序 多文件上传Cloudant如何保证操作顺序
问题说明
需求为通过HTML多文件输入控件获取选中文件,保存至Cloudant,现有代码存在两类异常:
- 执行顺序异常:文件尚未完成处理达到可发送状态时,代码就已执行到提交请求步骤,控制台中
doc1、doc2的日志打印时间早于blob日志 - 转换结果异常:所有文件经处理后得到的base64结果完全相同
已学习Promise相关知识并尝试多种实现方案,仍无法修正操作顺序错乱问题,原有实现代码如下:
function sendFile() { var doc = {}; doc._attachments = {}; doc._id = "testing::" + new Date().getTime(); doc.type = "testing attachment"; var blob = null; var url = fileName; url._rawValue.forEach(function (item) { console.log("item: ", item); fetch(item) .then((r) => r.blob()) .then((b) => { blob = b; console.log("blob: ", blob); return getBase64(blob); }) .then((blob2) => { console.log(blob2); let name = item.name; doc._attachments[name] = {}; doc._attachments[name].content_type = item.type; doc._attachments[name].data = blob2.split(",")[1]; }); console.log("doc1: ", doc); }); let doc2 = doc; console.log("doc2: ", doc2); api({ method: "POST", url: "/webdata", data: doc, }) .then((response) => { console.log("result: ", response); alert("Test has been submitted!"); }) .catch((e) => { console.log("e: ", e); alert(e); }); console.log("finished send test"); }
问题根因
- 执行顺序错乱:
forEach循环内的fetch、blob转换、base64编码全是异步操作,代码没有等待这些异步操作执行完成,就直接运行了后续的打印、提交请求等同步代码。根据JS事件循环机制,同步代码会先于异步微任务执行,自然会出现doc1/doc2日志早于blob日志、提交时附件数据为空的问题。 - base64结果重复:你在循环外层定义了全局共享的
blob变量,多个异步回调执行时会反复覆盖这个变量的值,加之上一轮异步赋值还没完成下一轮循环就已经启动,最终所有附件写入的都是最后一次赋值的blob对应的base64结果。
修正方案
将函数改为async异步写法,通过Promise.all等待所有文件处理完成后再发起提交请求,同时移除全局共享的blob变量,每个文件的处理逻辑保持作用域独立,修正后代码如下:
async function sendFile() { // 初始化Cloudant文档结构 const doc = { _attachments: {}, _id: "testing::" + Date.now(), type: "testing attachment" }; const fileList = fileName._rawValue; // 等待所有文件的读取、转码流程全部完成 await Promise.all(fileList.map(async (item) => { console.log("item: ", item); const res = await fetch(item); // 每个文件的blob存在当前回调作用域,不会互相覆盖 const blob = await res.blob(); console.log("blob: ", blob); const base64Data = await getBase64(blob); console.log(base64Data); doc._attachments[item.name] = { content_type: item.type, data: base64Data.split(",")[1] }; console.log("doc1: ", doc); })); const doc2 = doc; console.log("doc2: ", doc2); // 所有附件处理完毕后再发起提交 try { const response = await api({ method: "POST", url: "/webdata", data: doc, }); console.log("result: ", response); alert("Test has been submitted!"); } catch (e) { console.log("e: ", e); alert(e); } console.log("finished send test"); }
关键修正点
- 用
Promise.all搭配map替代原有的forEach遍历,统一等待所有异步文件处理流程结束后再执行提交逻辑,彻底解决执行顺序错乱问题 - 移除循环外层的共享
blob变量,每个文件的blob、base64结果都在当前遍历项的独立作用域内生成,从根源避免变量覆盖导致的转码结果重复问题 - 用async/await替代原有的长链式Promise调用,代码逻辑更线性,作用域边界更清晰,减少异步状态混乱问题
内容的提问来源于stack exchange,提问作者Morgan Hayes
相关产品推荐
相关产品推荐

