4 种姿势

1. try / catch

try {
    JSON.parse(badInput);
} catch (err) {
    console.error('解析失败:', err.message);
}

2. try / catch / finally

let conn;
try {
    conn = await db.connect();
    await conn.query('SELECT 1');
} catch (err) {
    console.error(err);
} finally {
    if (conn) await conn.close();           // 永远跑
}

3. 抛错

throw new Error('Something broke');
throw new TypeError('Expected string');
throw new RangeError('Out of bounds');

4. Promise 错误(异步章节细讲)

asyncFn().catch(err => console.error(err));

Error 类家族

Node 内置:

含义
Error 通用
TypeError 类型不对
RangeError 数字 / 索引超范围
SyntaxError 语法错(很少手动抛)
ReferenceError 引用未定义
URIError URI 相关
try {
    null.foo;
} catch (err) {
    console.log(err instanceof TypeError);   // true
    console.log(err.name);                    // 'TypeError'
    console.log(err.message);                 // "Cannot read properties of null (reading 'foo')"
    console.log(err.stack);                    // 调用栈
}

自定义错误

class ValidationError extends Error {
    constructor(field, message) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

try {
    throw new ValidationError('email', 'Invalid format');
} catch (err) {
    if (err instanceof ValidationError) {
        console.log(`${err.field}: ${err.message}`);
    } else {
        throw err;       // 不认识的错误重抛
    }
}

async / await 里的错误

async function getUser(id) {
    try {
        const r = await fetch(`/users/${id}`);
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
    } catch (err) {
        console.error('获取失败:', err);
        throw err;       // 传给调用方
    }
}

async 函数里 throw 等同 Promise reject——上游必须 await 或 .catch 才会捕获。

错误聚合(Node 15+)

const results = await Promise.allSettled([fn1(), fn2(), fn3()]);
const errors = results.filter(r => r.status === 'rejected').map(r => r.reason);
if (errors.length) {
    throw new AggregateError(errors, 'Some operations failed');
}

未捕获错误

Node 进程级捕获兜底:

process.on('uncaughtException', (err) => {
    console.error('💥 Uncaught:', err);
    // 通常应该 process.exit(1) 让 systemd / pm2 重启
    process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
    console.error('💥 Unhandled Rejection:', reason);
    process.exit(1);
});

这些是最后防线——不要用它们当正常流程错误处理。

错误传播规范

// ✓ 让错误冒到能处理的地方
async function readConfig() {
    return JSON.parse(await fs.readFile('config.json', 'utf8'));
    // 让调用方决定怎么处理
}

// ❌ 吞掉错误
async function readConfigBad() {
    try {
        return JSON.parse(await fs.readFile('config.json', 'utf8'));
    } catch (err) {
        return {};                  // 静默失败 → bug 难找
    }
}

// ✓ 转换并重抛
async function readConfigOk() {
    try {
        return JSON.parse(await fs.readFile('config.json', 'utf8'));
    } catch (err) {
        throw new Error(`Config load failed: ${err.message}`);
    }
}

实用工具:error.cause

try {
    await db.query(...);
} catch (err) {
    throw new Error('Failed to fetch user', { cause: err });
    // 保留原始错误的堆栈和上下文
}

打印时:

console.error(err);
// Error: Failed to fetch user
//     at ...
//   [cause]: Error: connection refused
//     at ...

  • 别忘记重抛不认识的错误(instanceof 判断后)—— 不然吃掉错的根因
  • async 函数没 await 就不会捕获——fn() 抛错变成 unhandled rejection
  • try 块里只 try 可能抛错的部分——别把整段函数包起来
  • 错误信息别泄露敏感数据(密码、token、内部路径)

下一篇起进入异步章节:callbacks → promises → async/await → event loop。