FileReader.readAsDataURL异步不等待,请求提前发送问题求助
FileReader onloadend 回调晚于 fetch 请求执行,Promise 实现未解决问题
开发图片上传(类似更换头像)功能时,使用FileReader读取文件时出现执行顺序异常:onloadend回调在fetch请求获得响应后才执行,导致请求未携带预期的Base64字符串。尝试过async/await、回调函数及Promise API均未解决,耗时9小时。
原始代码
最初的async/await版本转换函数
async function getB64StringFromFile(fileIn) { var reader = new FileReader(); reader.onloadend = function () { return reader.result; } if (fileIn && (fileIn.type == "image/jpeg" || fileIn.type == "image/png" && fileIn.size < 1 * Math.pow(2, 20))) { await reader.readAsDataURL(fileIn); } else { console.log("bad"); } }
主函数
async function updateBook(ev) { var form = ev.target.closest("form"); var data = new FormData(form); /*long section of code that is irrelevant to the problem*/ var objectData = Object.fromEntries(data); //the image as a file -- which already proved to be working let file = document.querySelector('input[type=file]#imageInput').files[0]; if (file) { objectData.ImageB64String = await getB64StringFromFile(file); } fetch("/inventory/update/", { method: "POST", contentType: "application/json; charset=utf-8", body: JSON.stringify(objectData), headers: { "Content-Type": "application/json", "RequestVerificationToken": csrfToken, }, }).then((res) => { ... }) }
当前执行顺序
- 调用
updateBook() - 调用
getB64StringFromFile() - 执行
Fetch请求 - 收到控制器响应(未携带Base64字符串)
- 回调
onloadend并返回字符串
期望执行顺序
- 调用
updateBook() - 调用
getB64StringFromFile() - 等待
onloadend准备完成 - 获取
onloadend返回的结果 - 执行
Fetch请求
补充:Promise版本代码(仍未解决)
function getB64StringFromFile(fileIn) { return new Promise(function (fulfilled, rejected) { var reader = new FileReader(); var string; reader.onloadend = function () { string = reader.result; fulfilled(string); } if (fileIn && (fileIn.type == "image/jpeg" || fileIn.type == "image/png" && fileIn.size < 1 * Math.pow(2, 20))) { reader.readAsDataURL(fileIn); } else { console.log("bad"); } }) }
问题分析与解决方案
1. 最初async/await版本的核心错误
reader.readAsDataURL()不返回Promise,因此await对它完全无效。函数执行到await这一行时会直接跳过,最终返回undefined(无return语句),导致主函数中await getB64StringFromFile(file)拿到undefined后立即执行fetch,而FileReader的异步回调在之后才触发。
2. Promise版本的问题
- 未处理错误分支:文件不符合类型/大小要求时,仅执行
console.log("bad")但未调用rejected(),导致Promise一直处于pending状态,后续代码可能异常执行。 - 逻辑运算符优先级隐患:
||优先级低于&&,原判断条件可读性差且易出错。 - 未阻止表单默认提交行为:如果
updateBook是表单提交事件处理函数,未调用ev.preventDefault()会触发浏览器默认同步提交,打乱异步执行流程(这很可能是回调顺序异常的关键原因)。
修正后的代码
正确的Promise版转换函数
function getB64StringFromFile(fileIn) { return new Promise((resolve, reject) => { // 判断文件是否存在 if (!fileIn) { reject(new Error("未选择文件")); return; } // 明确逻辑分组,提升可读性 const isValidFile = (fileIn.type === "image/jpeg" || fileIn.type === "image/png") && fileIn.size < 1 * Math.pow(2, 20); // 1MB if (!isValidFile) { console.log("bad"); reject(new Error("文件类型错误或大小超过1MB")); return; } const reader = new FileReader(); // 成功回调 reader.onloadend = () => { resolve(reader.result); }; // 错误回调,避免Promise一直pending reader.onerror = () => { reject(reader.error); }; reader.readAsDataURL(fileIn); }); }
修正后的主函数
async function updateBook(ev) { // 必须阻止表单默认提交行为,否则会打乱异步流程 ev.preventDefault(); const form = ev.target.closest("form"); const data = new FormData(form); const objectData = Object.fromEntries(data); const file = document.querySelector('input[type=file]#imageInput').files[0]; if (file) { try { // 等待Promise resolve拿到Base64字符串 objectData.ImageB64String = await getB64StringFromFile(file); } catch (err) { console.error("文件处理失败:", err); // 可添加用户提示,如alert(err.message) return; // 终止后续请求 } } try { // 等待fetch请求完成 const res = await fetch("/inventory/update/", { method: "POST", contentType: "application/json; charset=utf-8", body: JSON.stringify(objectData), headers: { "Content-Type": "application/json", "RequestVerificationToken": csrfToken, }, }); // 处理响应,根据实际情况调整 const result = await res.json(); console.log("更新成功:", result); // 可添加页面更新逻辑,如刷新列表 } catch (fetchErr) { console.error("请求失败:", fetchErr); } }
关键注意点
- 基于回调的API(如
FileReader)必须手动封装为Promise才能用await等待。 - 表单提交事件必须调用
ev.preventDefault(),避免浏览器默认同步提交干扰异步流程。 - 必须处理Promise的
reject分支,避免未处理的异常导致逻辑混乱。
内容的提问来源于stack exchange,提问作者MGC_StackOverflow
相关产品推荐
相关产品推荐

