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

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没有正确等待完成,导致后续操作提前执行:

  1. 保存Firestore文档的map返回的Promise数组没有用Promise.all包裹,.then直接走了下一步,此时文档可能还没保存完,后续操作就已经开始执行
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 04:54:01