您现在的位置是:网站首页 > 错误处理策略文章详情

错误处理策略

在Node.js开发中,错误处理是保证应用健壮性的关键环节。不同的场景需要针对性的策略,从同步/异步错误捕获到进程级管理,合理的错误处理能显著提升系统容错能力。

同步错误处理

同步代码中的错误通常通过try/catch块捕获。Node.js的同步API(如fs.readFileSync)会直接抛出异常:

const fs = require('fs');

function readConfigSync(path) {
  try {
    const data = fs.readFileSync(path, 'utf8');
    return JSON.parse(data);
  } catch (err) {
    console.error('同步操作失败:', err.message);
    // 恢复默认配置
    return { port: 3000 };
  }
}

典型错误类型包括:

  • SyntaxError:JSON解析失败
  • TypeError:参数类型错误
  • SystemError:文件系统操作失败

异步错误处理

回调模式

传统回调函数遵循Error-first约定,第一个参数总是错误对象:

fs.readFile('/nonexistent', (err, data) => {
  if (err) {
    if (err.code === 'ENOENT') {
      console.warn('文件不存在,使用默认值');
    } else {
      console.error('不可恢复错误:', err.stack);
      process.exit(1);
    }
    return;
  }
  // 正常处理逻辑
});

Promise处理

现代Node.js推荐使用Promise链式调用:

import { promises as fs } from 'fs';

async function loadUserData(userId) {
  return fs.readFile(`users/${userId}.json`, 'utf8')
    .then(JSON.parse)
    .catch(err => {
      if (err.code === 'ENOENT') {
        return { id: userId, status: 'new' };
      }
      throw err; // 重新抛出未处理错误
    });
}

异步函数错误处理

async/await语法需要显式try-catch:

async function processOrder(orderId) {
  try {
    const order = await db.getOrder(orderId);
    const receipt = await generateReceipt(order);
    await email.send(order.userEmail, receipt);
  } catch (err) {
    if (err instanceof DatabaseError) {
      await logDatabaseFailure(err);
    } else if (err instanceof EmailError) {
      await queueRetry(orderId);
    } else {
      // 未预期错误
      await criticalErrorHandler(err);
      throw err;
    }
  }
}

事件发射器错误处理

EventEmitter需要监听error事件:

const { EventEmitter } = require('events');
class MyEmitter extends EventEmitter {}

const emitter = new MyEmitter();
emitter.on('error', (err) => {
  console.error('发射器错误:', err.message);
  // 防止进程崩溃
});

// 未处理error事件会导致进程退出
emitter.emit('error', new Error('测试错误')); 

进程级错误处理

未捕获异常

process.on('uncaughtException', (err) => {
  console.error('致命错误:', err);
  // 记录状态后优雅退出
  emergencyLogger.fatal(err).finally(() => {
    process.exit(1);
  });
});

未处理的Promise拒绝

process.on('unhandledRejection', (reason, promise) => {
  console.warn('未处理的拒绝:', reason);
  // 可以转换为未捕获异常
  throw reason;
});

错误分类策略

可恢复错误

class RetryableError extends Error {
  constructor(message, { maxRetries = 3 } = {}) {
    super(message);
    this.retryable = true;
    this.maxRetries = maxRetries;
  }
}

async function fetchWithRetry(url) {
  let lastError;
  for (let i = 0; i < 3; i++) {
    try {
      return await fetch(url);
    } catch (err) {
      lastError = err;
      if (!err.retryable) break;
      await sleep(1000 * (i + 1));
    }
  }
  throw lastError;
}

业务逻辑错误

class ValidationError extends Error {
  constructor(field, message) {
    super(`字段验证失败: ${field}`);
    this.field = field;
    this.userMessage = message;
    this.statusCode = 400;
  }
}

router.post('/users', (req, res) => {
  try {
    validateUserInput(req.body);
    // ...
  } catch (err) {
    if (err instanceof ValidationError) {
      return res.status(err.statusCode).json({ error: err.userMessage });
    }
    next(err);
  }
});

错误日志记录

结构化日志示例:

const { createLogger, transports, format } = require('winston');

const errorLogger = createLogger({
  level: 'error',
  format: format.combine(
    format.timestamp(),
    format.errors({ stack: true }),
    format.json()
  ),
  transports: [
    new transports.File({ filename: 'error.log' }),
    new transports.Console({
      format: format.prettyPrint()
    })
  ]
});

// 使用示例
try {
  riskyOperation();
} catch (err) {
  errorLogger.error('操作失败', {
    error: err.message,
    stack: err.stack,
    context: { userId: 123 }
  });
}

HTTP错误处理中间件

Express中间件示例:

function errorMiddleware(err, req, res, next) {
  // 识别错误类型
  const status = err.statusCode || 500;
  const response = {
    message: status >= 500 ? '服务器错误' : err.message
  };

  // 开发环境显示堆栈
  if (process.env.NODE_ENV === 'development') {
    response.stack = err.stack;
  }

  // 特殊错误类型处理
  if (err instanceof MongooseError) {
    response.details = err.errors;
  }

  res.status(status).json(response);
  
  // 触发监控系统
  if (status >= 500) {
    monitoring.notify(err);
  }
}

app.use(errorMiddleware);

资源清理策略

let resource;
try {
  resource = acquireResource();
  await useResource(resource);
} finally {
  if (resource) {
    await resource.release().catch(releaseErr => {
      console.error('资源释放失败:', releaseErr);
    });
  }
}

错误转换模式

将底层错误转换为业务错误:

async function getUserProfile(userId) {
  try {
    return await db.query('SELECT * FROM users WHERE id = ?', [userId]);
  } catch (err) {
    if (err.code === 'ER_NO_SUCH_TABLE') {
      throw new ApplicationError('系统维护中', { code: 'SERVICE_UNAVAILABLE' });
    }
    if (err.code === 'ER_BAD_FIELD_ERROR') {
      throw new ApplicationError('数据库结构异常', { code: 'INTERNAL_ERROR' });
    }
    throw err;
  }
}

防御性编程技巧

参数校验辅助函数:

function assert(condition, message, ErrorType = Error) {
  if (!condition) {
    throw new ErrorType(message);
  }
}

function createUser(userData) {
  assert(userData.email, '邮箱不能为空', ValidationError);
  assert(/^.+@.+\..+$/.test(userData.email), '邮箱格式无效', ValidationError);
  // ...
}

错误聚合报告

批量操作时的错误收集:

async function batchProcess(items) {
  const results = [];
  const errors = [];
  
  await Promise.all(items.map(async (item) => {
    try {
      results.push(await processItem(item));
    } catch (err) {
      errors.push({
        item,
        error: err.message
      });
    }
  }));

  if (errors.length > 0) {
    throw new BatchError('部分处理失败', { errors, results });
  }
  return results;
}

超时控制机制

async function withTimeout(promise, timeout, errorMessage) {
  let timer;
  const timeoutPromise = new Promise((_, reject) => {
    timer = setTimeout(() => {
      reject(new TimeoutError(errorMessage));
    }, timeout);
  });

  try {
    return await Promise.race([promise, timeoutPromise]);
  } finally {
    clearTimeout(timer);
  }
}

// 使用示例
try {
  const data = await withTimeout(
    fetchAPI(),
    5000,
    'API响应超时'
  );
} catch (err) {
  if (err instanceof TimeoutError) {
    // 启动备用方案
  }
}

错误恢复模式

数据库连接重试示例:

class DatabaseConnection {
  constructor() {
    this.retryCount = 0;
    this.maxRetries = 5;
  }

  async connect() {
    while (this.retryCount < this.maxRetries) {
      try {
        return await this._attemptConnect();
      } catch (err) {
        this.retryCount++;
        if (this.retryCount >= this.maxRetries) {
          throw new Error(`连接失败,已重试${this.maxRetries}次`);
        }
        await this._waitForRetry();
      }
    }
  }

  async _attemptConnect() {
    // 实际连接逻辑
  }

  async _waitForRetry() {
    const delay = Math.min(1000 * 2 ** this.retryCount, 30000);
    await new Promise(resolve => setTimeout(resolve, delay));
  }
}

错误边界设计

React组件错误边界在Node.js的类似实现:

function withErrorBoundary(component) {
  return async function (...args) {
    try {
      return await component(...args);
    } catch (err) {
      await errorService.report(err);
      return {
        status: 'error',
        message: '功能暂时不可用',
        errorId: err.reportId
      };
    }
  };
}

// 使用装饰器
const safeHandler = withErrorBoundary(async (req, res) => {
  // 业务逻辑
});

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步