Firebase Storage图片URL列表上传Cloud Firestore为空问题排查
Firebase 上传URL列表写入Firestore为空问题
问题现象
一次性上传多张图片到Firebase Cloud Storage,上传完成后将所有图片URL存入urlsList变量,再把这个列表上传到Cloud Firestore。所有步骤看似正常,但Firestore中始终显示为空列表,而控制台打印的urlsList却包含所有图片URL。
相关截图
- 数据库截图:

- 控制台日志截图:

原代码示例
const uplaodImage = async () => { const promises = [] let urlsList = [] if (files.length > 0) { setLoading(true) files.map((file) => { console.log('loop'); const storageRef = ref(storage, `juxtaposition/${currentUser?.uid}/${currentJob?.jobID}/${file.name}`); const uploadTask = uploadBytesResumable(storageRef, file); promises.push(uploadTask) uploadTask.on( "state_changed", (snapshot) => { const prog = Math.round( (snapshot.bytesTransferred / snapshot.totalBytes) * 100 ); setProgressPercent(prog); }, (err) => dispatch(openSnackBar(err.message)), async () => { await getDownloadURL(uploadTask.snapshot.ref).then((downloadURLs) => { urlsList.push(downloadURLs) console.log("File available at", downloadURLs); }); } ); }) Promise.all(promises) .then(async () => { try { console.log(urlsList) const collectionRef = doc(db, "prevJobs", currentUser?.uid, "pendingJobs", currentJob?.jobID) const deleteRef = doc(db, 'currentJobs', currentUser?.uid, 'yourJobs', currentJob?.jobID) await setDoc(collectionRef, { ...currentJob, juxtapositionImage: urlsList, manufacturerID: currentUser?.uid, status: 'pending' }) deleteDoc(deleteRef) } catch (err) { setLoading(false) dispatch(openSnackBar(err.message)) } }) .then(() => { setLoading(false) closeDialog(false) // navigate('/currentjobs', { replace: true }) }) } else { setLoading(false) setError(true) } }
问题原因
核心问题是异步时序不匹配:uploadBytesResumable返回的Promise在文件上传完成后立即resolve,但此时getDownloadURL的异步回调还未执行完毕。当Promise.all(promises)触发Firestore写入逻辑时,urlsList还处于未完全填充的状态。而控制台打印的urlsList看起来有值,是因为控制台会动态更新引用类型的内容,实际写入Firestore时用的是当时的空数组快照。
修复方案
将每个文件的「上传操作+获取下载URL」逻辑封装成独立的Promise,确保只有当成功获取到下载URL后,该Promise才会resolve。这样Promise.all就能等待所有URL都获取完成,再执行Firestore写入。
修改后的代码
const uploadImage = async () => { // 先处理空文件情况,提前返回 if (files.length === 0) { setLoading(false); setError(true); return; } setLoading(true); // 为每个文件生成包含上传+获取URL的Promise const urlPromises = files.map((file) => { return new Promise((resolve, reject) => { const storageRef = ref(storage, `juxtaposition/${currentUser?.uid}/${currentJob?.jobID}/${file.name}`); const uploadTask = uploadBytesResumable(storageRef, file); uploadTask.on( "state_changed", (snapshot) => { const prog = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100); setProgressPercent(prog); }, // 上传失败时reject (err) => { dispatch(openSnackBar(err.message)); reject(err); }, async () => { try { const downloadURL = await getDownloadURL(uploadTask.snapshot.ref); console.log("File available at", downloadURL); // 获取URL成功后resolve,将URL作为结果返回 resolve(downloadURL); } catch (err) { dispatch(openSnackBar(err.message)); reject(err); } } ); }); }); try { // 等待所有URL获取完成,直接得到完整的urlsList const urlsList = await Promise.all(urlPromises); console.log(urlsList); const collectionRef = doc(db, "prevJobs", currentUser?.uid, "pendingJobs", currentJob?.jobID); const deleteRef = doc(db, 'currentJobs', currentUser?.uid, 'yourJobs', currentJob?.jobID); // 写入Firestore await setDoc(collectionRef, { ...currentJob, juxtapositionImage: urlsList, manufacturerID: currentUser?.uid, status: 'pending' }); // 确保删除操作完成再继续 await deleteDoc(deleteRef); setLoading(false); closeDialog(false); // navigate('/currentjobs', { replace: true }) } catch (err) { setLoading(false); dispatch(openSnackBar(err.message)); } };
关键改动说明
- Promise封装上传流程:每个文件的上传和URL获取逻辑被包装成独立Promise,确保只有拿到URL后才resolve,避免时序错误
- 直接获取URL数组:通过
await Promise.all(urlPromises)直接得到完整的URL列表,不再依赖外部变量的异步填充 - 完善异步操作:给
deleteDoc添加await,保证删除操作完成后再执行后续步骤 - 优化代码结构:提前处理空文件场景,减少嵌套层级,提升代码可读性
内容的提问来源于stack exchange,提问作者Jovane Mullings
相关产品推荐
相关产品推荐

