async/await代码执行顺序异常:本地URL数组转base64时结果数组始终为空
问题根因
空数组、[undefined]的问题本质是异步时序没有被正确阻塞,和async函数包裹后的直觉执行预期不符:
- 原有代码仅对
fetch和blob()的Promise做了await处理,但FileReader的onloadend是事件驱动的异步回调,本身没有返回Promise,完全不会被async函数的执行流拦截等待 - 代码实际执行顺序:
- 每轮for循环同步执行:创建FileReader、调用
readAsDataURL(把文件读取任务排入事件队列)、给onloadend赋值回调,单轮循环没有等待读取完成就直接进入下一轮 - 整个for循环跑完后,立刻执行
console.log(uploadedBase64Images),这时候所有FileReader的onloadend回调都还在事件队列里没有触发,数组自然是空的 - 等同步执行流全部跑完,事件循环才会依次触发每个
onloadend回调,这时候才会开始往数组里push数据,但日志已经输出完了
- 每轮for循环同步执行:创建FileReader、调用
- 把push逻辑移到回调外部时,代码执行到
push(base64)那行,onloadend回调还没执行,base64变量还停留在初始的空值/undefined状态,所以数组拿到的自然是undefined。
解决方法
核心思路是把FileReader的读取流程封装成标准Promise,这样就能被async/await正确拦截等待:
先定义通用的blob转base64工具函数:
function blobToBase64(blob) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }) }替换原有循环逻辑,直接await封装好的转换流程:
let uploadedBase64Images = []; for (let i = 0; i < urls.length; i++) { const imgRes = await fetch(urls[i]); const imgBlob = await imgRes.blob(); // 这一步会等待FileReader读取完成,拿到base64结果再往下执行 const base64 = await blobToBase64(imgBlob); uploadedBase64Images.push(base64); } // 此时所有转换都已完成,日志输出的就是完整的base64数组 console.log(uploadedBase64Images)如果需要优化性能、减少串行等待的时间,可以用
Promise.all并行处理所有URL的转换:// 并行发起所有请求和读取,等全部完成后一次性返回结果数组 const uploadedBase64Images = await Promise.all( urls.map(async (url) => { const imgRes = await fetch(url); const imgBlob = await imgRes.blob(); return blobToBase64(imgBlob); }) ) console.log(uploadedBase64Images)
注意:如果单个页面需要处理的图片数量很多,并行请求时要注意控制并发数,避免一次性发起过多请求触发浏览器的同源请求数限制。
内容的提问来源于stack exchange,提问作者clueless waffle
相关产品推荐
相关产品推荐

