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

如何在main函数中捕获Promise.all包裹的递归上传函数错误

问题描述

需要实现多文件分片同时上传,每个分片上传失败时最多重试5次。当前递归的uploadFile函数被Promise.all包裹调用,但无法在main函数的catch块中正确捕获错误,请问该如何修复?

示例代码(简化模拟请求逻辑):

class FileUploader {
    onSuccess: ((this: FileUploader) => void) | null;
    onFail: ((this: FileUploader) => void) | null;

    constructor() {
        this.onSuccess = null;
        this.onFail = null;
    }

    run() {
        // 模拟请求失败,实际场景是随机成功/失败
        this.onFail?.();
    }
}

async function uploadFile(name: string, retryCount = 0) {
    return new Promise<void>(async (resolve, reject) => {
        const runner = new FileUploader();
        runner.onSuccess = async () => {
            await new Promise((res) => setTimeout(res, 500));
            resolve();
        }
        runner.onFail = async () => {
            if (retryCount < 5) {
                console.log(`文件${name}第${retryCount+1}次重试`);
                const newDelay = Math.pow(2, retryCount) * 100;
                await new Promise((res) => setTimeout(res, newDelay));
                await uploadFile(name, retryCount + 1);
            }
            console.log("rejected", name);
            reject("error");
        }
        runner.run();
    });
}

async function main() {
    try {
        await Promise.all([uploadFile("file1"), uploadFile("file2"), uploadFile("file3")])
        console.log("所有文件上传成功")
    }
    catch (ex) {
        console.log("捕获到错误")
        console.log(ex);
    }
}

main();
问题根源

当前代码的核心问题在于:

  • 重试时递归调用uploadFile,但没有将递归后的Promise状态(成功/失败)关联到当前Promise的resolve/reject,导致无论重试是否成功,当前Promise都会执行reject。
  • 返回的Promise内部使用了async,造成不必要的Promise嵌套,增加了状态管理的复杂度。
修复方案
  1. 关联重试的Promise状态:当需要重试时,调用递归的uploadFile后,将其结果传递给当前Promise的resolve或reject,而不是直接触发reject。
  2. 移除Promise内部的async:避免Promise嵌套,简化状态流转逻辑。
  3. 调整重试后的逻辑:只有当重试次数耗尽时,才执行reject;否则,将递归调用的结果绑定到当前Promise。
修正后的代码
class FileUploader {
    onSuccess: ((this: FileUploader) => void) | null;
    onFail: ((this: FileUploader) => void) | null;

    constructor() {
        this.onSuccess = null;
        this.onFail = null;
    }

    run() {
        // 可以打开下面的注释模拟随机成功/失败
        // const num = Math.random();
        // if (num > 0.4) {
        //     this.onSuccess?.();
        // } else {
        //     this.onFail?.();
        // }
        this.onFail?.();
    }
}

function uploadFile(name: string, retryCount = 0) {
    return new Promise<void>((resolve, reject) => {
        const runner = new FileUploader();
        runner.onSuccess = () => {
            setTimeout(() => {
                console.log(`文件${name}上传成功`);
                resolve();
            }, 500);
        };
        runner.onFail = () => {
            if (retryCount < 5) {
                console.log(`文件${name}第${retryCount + 1}次重试`);
                const newDelay = Math.pow(2, retryCount) * 100;
                setTimeout(async () => {
                    try {
                        // 将递归上传的结果绑定到当前Promise的resolve/reject
                        await uploadFile(name, retryCount + 1);
                        resolve();
                    } catch (err) {
                        reject(err);
                    }
                }, newDelay);
            } else {
                console.log(`文件${name}重试5次后仍失败`);
                reject(new Error(`文件${name}上传失败`));
            }
        };
        runner.run();
    });
}

async function main() {
    try {
        await Promise.all([uploadFile("file1"), uploadFile("file2"), uploadFile("file3")]);
        console.log("所有文件分片上传完成");
    } catch (ex) {
        console.log("全局捕获到上传错误:");
        console.log(ex);
    }
}

main();
关键修改说明
  • 移除了uploadFile函数的async修饰符,以及内部Promise的async,避免Promise嵌套导致的状态混乱。
  • 在重试逻辑中,通过try/catch捕获递归uploadFile的结果,成功则调用当前Promise的resolve,失败则调用reject,确保当前Promise的状态与重试结果一致。
  • 只有当重试次数达到5次上限时,才主动触发reject,此时错误会被Promise.all捕获,最终传递到main函数的catch块中。

内容的提问来源于stack exchange,提问作者3Dmu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 03:43:25