React+Redux项目Firebase Storage移动恢复文件出现异常报错
React+Redux项目使用Firebase Storage移动关联文件报错问题
我在React.js+Redux项目中使用Firebase相关服务,需求是当用户删除主集合时,将Firebase Storage中的所有关联文件移动到同实例下的另一路径,运行时出现如下报错:
我编写的实现函数如下:
const restoreDeletedFile = ( storageRef, fileArr, personRecordId, metadata, convertToOriginalPath = false ) => { // 遍历文件元数据 let restoreFilesPromises = fileArr.map((file, index) => { return storageRef .child(file.data().path) .getDownloadURL() .then((url) => { // url是文件的下载地址,可以直接下载 var xhr = new XMLHttpRequest(); xhr.responseType = "blob"; xhr.onload = () => { let fileToUpload = xhr.response; // 上传文件到新路径后删除旧路径文件 // 新路径格式:pr/id/image.jpeg // 旧路径格式:deletedPR/pr/image.jpeg return storageRef .child( formatFilePath( file.data(), personRecordId, FILE_PATH.ORIGINAL, convertToOriginalPath ) ) .put(fileToUpload, metadata[index]) .then(() => storageRef.child(file.data().path).delete() ); }; xhr.open("GET", url); xhr.send(); return url; }); }); return Promise.all(restoreFilesPromises); };
这个函数单独恢复一组文件时可以正常运行,但删除主集合后尝试恢复所有关联集合的已删除文件时,就无法正常工作,不会执行文件移动操作。我添加了多处console log调试,所有参数都符合预期,没有发现异常。
调用该函数的代码如下:
.then(() => // 获取人员记录文件 firestore .collection("personRecords") .doc("*deleted") .collection("deletedRecords") .doc(personRecordId) .collection("files") .get() ) .then((docs) => { fileArr = docs.docs; // 这一步文档保存正常,我已经核对过Firestore的文档内容 fileArr.map((doc) => { return firestore .collection("personRecords") .doc(personRecordId) .collection("files") .doc() .set({ docSource: doc.data().docSource, lastModifiedDate: doc.data().lastModifiedDate, name: doc.data().name, path: formatFilePath( doc.data(), personRecordId, FILE_PATH.ORIGINAL ), size: doc.data().size, type: doc.data().type, uploaderId: doc.data().uploaderId, }); }); }) .then(() => { // 获取文件元数据 let getDeletedMetaDataPromises = fileArr.map((file, index) => { return storageRef .child(file.data().path) .getMetadata(); }); return Promise.all( getDeletedMetaDataPromises ); }) .then((metadataSnapshot) => { // 后续会用到元数据 metadata = metadataSnapshot.customMetadata; return metadataSnapshot; }) .then(() => // 我已经打印过所有参数,都正常,但还是报上面的错误 restoreDeletedFile( storageRef, fileArr, personRecordId, metadata ) )
问题原因与解决方案
核心问题1:Promise链未正确捕获异步操作
代码里有两处Promise没有正确等待完成,导致后续操作提前执行:
- 保存Firestore文档的
map返回的Promise数组没有用Promise.all包裹,.then直接走了下一步,此时文档可能还没保存完,后续操作就已经开始执行 restoreDeletedFile里的XMLHttpRequest是异步操作,你返回的是url而不是XHR内部的上传+删除操作的Promise,导致Promise.all(restoreFilesPromises)会在XHR发送后直接resolve,不等上传删除操作完成,批量操作时就会出现逻辑中断,也会触发CORS或者权限类报错。
核心问题2:元数据获取错误
你在处理metadataSnapshot的时候直接取了customMetadata,但metadataSnapshot是元数据数组,每一项的customMetadata才是对应文件的自定义元数据,直接赋值给metadata会导致restoreDeletedFile里取metadata[index]是undefined,上传时参数错误。
修复方案
1. 修复Promise链等待逻辑
把保存文档的map部分用Promise.all包裹:
.then((docs) => { fileArr = docs.docs; // 等待所有文档保存完成再走下一步 return Promise.all(fileArr.map((doc) => { return firestore .collection("personRecords") .doc(personRecordId) .collection("files") .doc() .set({ docSource: doc.data().docSource, lastModifiedDate: doc.data().lastModifiedDate, name: doc.data().name, path: formatFilePath( doc.data(), personRecordId, FILE_PATH.ORIGINAL ), size: doc.data().size, type: doc.data().type, uploaderId: doc.data().uploaderId, }); })); })
2. 修复元数据赋值逻辑
.then((metadataSnapshot) => { // 取每个文件的元数据组成数组 metadata = metadataSnapshot.map(item => item.customMetadata); return metadataSnapshot; })
3. 重写restoreDeletedFile,正确返回Promise
不用XMLHttpRequest,直接用Storage SDK自带的方法更稳定,也避免异步封装错误:
const restoreDeletedFile = async ( storageRef, fileArr, personRecordId, metadata, convertToOriginalPath = false ) => { const restoreFilesPromises = fileArr.map(async (file, index) => { const oldRef = storageRef.child(file.data().path); // 直接获取文件blob,不用手动发XHR const fileBlob = await oldRef.getBlob(); const newPath = formatFilePath( file.data(), personRecordId, FILE_PATH.ORIGINAL, convertToOriginalPath ); const newRef = storageRef.child(newPath); // 上传新文件 await newRef.put(fileBlob, metadata[index]); // 删除旧文件 await oldRef.delete(); return newPath; }); return Promise.all(restoreFilesPromises); };
如果修复后还有报错,检查Firebase Storage的安全规则是否允许你对新、旧路径都有读写权限,批量操作时可以分批处理,避免触发Storage的限流规则。
内容的提问来源于stack exchange,提问作者Salem Nusair
相关产品推荐
相关产品推荐

