到底用 `Promise.reject` 还是 `throw new Error`

384 阅读2分钟

Promise.rejectthrow new Error 在异步函数中的差异:

🔍 主要差异

1. 语法和行为差异

// 方式1: Promise.reject
async function upload1() {
    if (!files) {
        return Promise.reject('没有选择文件'); // 返回一个被拒绝的Promise
    }
}

// 方式2: throw new Error  
async function upload2() {
    if (!files) {
        throw new Error('没有选择文件'); // 抛出异常,自动包装成被拒绝的Promise
    }
}

2. 实际执行结果

// 测试代码
async function test() {
    try {
        await upload1(); // 会进入catch块
    } catch (error) {
        console.log('upload1 error:', error); // '没有选择文件'
    }
    
    try {
        await upload2(); // 也会进入catch块
    } catch (error) {
        console.log('upload2 error:', error); // Error: 没有选择文件
    }
}

�� 详细对比

特性Promise.rejectthrow new Error
语法显式返回Promise抛出异常
错误类型直接返回错误值抛出Error对象
堆栈跟踪可能丢失堆栈信息保留完整堆栈信息
调试体验较差更好
代码一致性混合使用统一异常处理
TypeScript支持需要额外类型处理更好的类型推断

�� 为什么推荐 throw new Error

1. 更好的调试体验

// Promise.reject 方式
async function badExample() {
    if (error) {
        return Promise.reject('Something went wrong');
    }
}

// throw new Error 方式  
async function goodExample() {
    if (error) {
        throw new Error('Something went wrong');
    }
}

// 调用时
try {
    await badExample(); // 错误信息: 'Something went wrong'
    await goodExample(); // 错误信息: Error: Something went wrong at goodExample
} catch (error) {
    console.log(error.stack); // throw方式有完整的堆栈信息
}

2. 代码一致性

// 混合使用 - 不推荐
async function inconsistent() {
    if (condition1) {
        return Promise.reject('error1');
    }
    
    if (condition2) {
        throw new Error('error2'); // 混用
    }
    
    if (condition3) {
        return Promise.reject('error3');
    }
}

// 统一使用 throw - 推荐
async function consistent() {
    if (condition1) {
        throw new Error('error1');
    }
    
    if (condition2) {
        throw new Error('error2');
    }
    
    if (condition3) {
        throw new Error('error3');
    }
}

3. TypeScript 类型安全

// Promise.reject 可能导致类型问题
async function typeIssue() {
    if (error) {
        return Promise.reject('string error'); // 返回 Promise<never>
    }
    return 'success'; // 返回 Promise<string>
    // 函数返回类型变成 Promise<string | never>
}

// throw new Error 类型更清晰
async function typeSafe() {
    if (error) {
        throw new Error('string error'); // 抛出异常,不影响返回类型
    }
    return 'success'; // 返回 Promise<string>
    // 函数返回类型保持 Promise<string>
}

🔧 实际应用场景

在异步函数中的最佳实践:

async function uploadImages(files: File[]) {
    // 参数验证
    if (!files || files.length === 0) {
        throw new Error('没有选择文件'); // ✅ 推荐
        // return Promise.reject('没有选择文件'); // ❌ 不推荐
    }
    
    // 业务逻辑错误
    if (!isValidFormat(files)) {
        throw new Error('不支持的文件格式'); // ✅ 推荐
    }
    
    try {
        // 网络请求等可能失败的操作
        const result = await fetch('/api/upload', { body: files });
        if (!result.ok) {
            throw new Error(`上传失败: ${result.statusText}`); // ✅ 推荐
        }
        return result.json();
    } catch (error) {
        // 重新抛出或处理错误
        throw new Error(`上传过程中发生错误: ${error.message}`);
    }
}

📝 总结

async/await 函数中,throw new ErrorPromise.reject 更推荐,因为:

  1. 语法更简洁 - 直接抛出异常,无需显式返回Promise
  2. 调试更友好 - 保留完整的错误堆栈信息
  3. 代码更一致 - 统一使用异常处理机制
  4. 类型更安全 - TypeScript 类型推断更准确
  5. 符合现代JavaScript最佳实践 - 大多数现代框架和库都推荐这种方式

这就是为什么我在代码检查中建议将 Promise.reject 改为 throw new Error 的原因!