You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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));
  }
};

关键改动说明

  1. Promise封装上传流程:每个文件的上传和URL获取逻辑被包装成独立Promise,确保只有拿到URL后才resolve,避免时序错误
  2. 直接获取URL数组:通过await Promise.all(urlPromises)直接得到完整的URL列表,不再依赖外部变量的异步填充
  3. 完善异步操作:给deleteDoc添加await,保证删除操作完成后再执行后续步骤
  4. 优化代码结构:提前处理空文件场景,减少嵌套层级,提升代码可读性

内容的提问来源于stack exchange,提问作者Jovane Mullings

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 12:50:28