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

React中Firebase表单首次提交存空URL,二次提交正常的问题求助

问题:Firebase Storage上传后实时数据库首次保存空URL的解决方法

我是Firebase数据库新手,在React项目中开发包含图片和视频文件的表单,期望将文件上传至Firebase Storage后,同步把下载URL保存到实时数据库。但首次提交表单时,文件成功上传至Storage,数据库中却存储空URL;第二次提交时,文件和URL均能正确保存。我尝试过使用async/await,但问题仍未解决,以下是处理上传和存储的代码片段:

const thumnailUpload = async () => {
    try {
      const storage = getStorage();
      const storageRef = ref_storage(
        storage,
        `products_test/${Date.now() + title}`
      );
      const uploadTask = uploadBytesResumable(storageRef, thumbnail);

      uploadTask.on(
        "state_changed",
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log("Thumbnail Upload is " + progress + "% done");
          switch (snapshot.state) {
            case "paused":
              console.log("Thumbnail Upload is paused");
              break;
            case "running":
              console.log("Thumbnail Upload is running");
              break;
          }
        },
        (error) => {
          toast.error(error.message);
        },
        async () => {
          let downloadUrlThumbnail1 = await getDownloadURL(
            uploadTask.snapshot.ref
          );
          setThumbnailUrl(downloadUrlThumbnail1);
          console.log("Thumbnail Upload is completed");
          console.log(thumbnailUrl);
          toast.success("Thumbnail Upload is completed");
        }
      );
    } catch (err) {
      console.log(err);
    }
  };

  const handleVideoUpload = async () => {
    try {
      const storage = getStorage();
      const storageRef = ref_storage(
        storage,
        `products_test/${Date.now() + title}`
      );

      const uploadTask = uploadBytesResumable(storageRef, mainContent);

      uploadTask.on(
        "state_changed",
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log("Video Upload is " + progress + "% done");
          switch (snapshot.state) {
            case "paused":
              console.log(" Video Upload is paused");
              break;
            case "running":
              console.log("Video Upload is running");
              break;
          }
        },
        (error) => {
          toast.error(error.message);
        },
        async () => {
          let downloadUrlVideo = await getDownloadURL(uploadTask.snapshot.ref);
          setContentUrl(downloadUrlVideo);
          console.log("Video Upload is completed");
          console.log(contentUrl);
          toast.success("Video Upload is completed");
        }
      );
    } catch (err) {
      console.log(err);
    }
  };

  const addProduct = async (e) => {
    e.preventDefault();
    handleVideoUpload();
    thumnailUpload();

    const writeUserData = async (
      title,
      description,
      price,
      category,
      type,
      thumbnailUrl,
      contentUrl
    ) => {
      const db = getDatabase();
      await set(ref_database(db, "testproducts/"), {
        title: title,
        description: description,
        contentType: type,
        category: category,
        price: price,
        imgUrl: thumbnailUrl,
        videoUrl: contentUrl,
        uploader: currentUser.uid,
      });
    };

    writeUserData(
      title,
      description,
      price,
      category,
      type,
      thumbnailUrl,
      contentUrl
    );
  };

问题原因

  1. 上传函数虽然标记为async,但内部依赖uploadBytesResumable的回调机制,函数会直接返回,不会等待上传完成,导致addProduct中调用上传函数后立刻执行数据库写入,此时thumbnailUrl和contentUrl还未被更新。
  2. React状态更新是异步操作,即使上传完成,也无法保证写入数据库时状态已经更新完成。

解决方案

修改上传函数,将其包装为Promise,返回下载URL;在addProduct中等待所有上传完成后,再用获取到的URL写入数据库。

修改后的上传函数

const thumnailUpload = async () => {
  return new Promise((resolve, reject) => {
    try {
      const storage = getStorage();
      const storageRef = ref_storage(
        storage,
        `products_test/${Date.now() + title}`
      );
      const uploadTask = uploadBytesResumable(storageRef, thumbnail);

      uploadTask.on(
        "state_changed",
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log("Thumbnail Upload is " + progress + "% done");
          switch (snapshot.state) {
            case "paused":
              console.log("Thumbnail Upload is paused");
              break;
            case "running":
              console.log("Thumbnail Upload is running");
              break;
          }
        },
        (error) => {
          toast.error(error.message);
          reject(error);
        },
        async () => {
          const downloadUrlThumbnail1 = await getDownloadURL(
            uploadTask.snapshot.ref
          );
          setThumbnailUrl(downloadUrlThumbnail1);
          console.log("Thumbnail Upload is completed");
          toast.success("Thumbnail Upload is completed");
          resolve(downloadUrlThumbnail1);
        }
      );
    } catch (err) {
      console.log(err);
      reject(err);
    }
  });
};

const handleVideoUpload = async () => {
  return new Promise((resolve, reject) => {
    try {
      const storage = getStorage();
      const storageRef = ref_storage(
        storage,
        `products_test/${Date.now() + title}`
      );

      const uploadTask = uploadBytesResumable(storageRef, mainContent);

      uploadTask.on(
        "state_changed",
        (snapshot) => {
          const progress =
            (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log("Video Upload is " + progress + "% done");
          switch (snapshot.state) {
            case "paused":
              console.log(" Video Upload is paused");
              break;
            case "running":
              console.log("Video Upload is running");
              break;
          }
        },
        (error) => {
          toast.error(error.message);
          reject(error);
        },
        async () => {
          const downloadUrlVideo = await getDownloadURL(uploadTask.snapshot.ref);
          setContentUrl(downloadUrlVideo);
          console.log("Video Upload is completed");
          toast.success("Video Upload is completed");
          resolve(downloadUrlVideo);
        }
      );
    } catch (err) {
      console.log(err);
      reject(err);
    }
  });
};

修改后的addProduct函数

const addProduct = async (e) => {
  e.preventDefault();
  try {
    // 等待两个上传任务完成,获取下载URL
    const [thumbnailUrl, contentUrl] = await Promise.all([
      thumnailUpload(),
      handleVideoUpload()
    ]);

    const writeUserData = async (
      title,
      description,
      price,
      category,
      type,
      thumbnailUrl,
      contentUrl
    ) => {
      const db = getDatabase();
      await set(ref_database(db, "testproducts/"), {
        title: title,
        description: description,
        contentType: type,
        category: category,
        price: price,
        imgUrl: thumbnailUrl,
        videoUrl: contentUrl,
        uploader: currentUser.uid,
      });
    };

    await writeUserData(
      title,
      description,
      price,
      category,
      type,
      thumbnailUrl,
      contentUrl
    );
    toast.success("产品信息保存成功");
  } catch (err) {
    console.error("保存失败:", err);
    toast.error("保存失败,请重试");
  }
};

关键修改点

  • 将上传函数包装为Promise,上传完成后resolve返回下载URL,出错时reject错误。
  • 使用Promise.all同时等待两个上传任务完成,确保获取到有效URL后再执行数据库写入。
  • 直接使用上传返回的URL写入数据库,避免依赖React异步状态更新的延迟问题。

内容的提问来源于stack exchange,提问作者Gilbert Intabo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:45:29