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 ); };
问题原因
- 上传函数虽然标记为
async,但内部依赖uploadBytesResumable的回调机制,函数会直接返回,不会等待上传完成,导致addProduct中调用上传函数后立刻执行数据库写入,此时thumbnailUrl和contentUrl还未被更新。 - 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
相关产品推荐
相关产品推荐

