Skip to content
On this page

Node.js 常见问题

本章汇总了Node.js开发和部署过程中遇到的常见问题及其解决方案。

安装和依赖问题

npm相关问题

npm安装失败

问题: npm install 失败,出现权限错误或网络错误。

解决方案:

bash
# 清除npm缓存
npm cache clean --force

# 更换npm镜像源
npm config set registry https://registry.npmmirror.com

# 或使用cnpm
npm install -g cnpm --registry=https://registry.npmmirror.com

# 检查npm配置
npm config list

# 重置npm配置
npm config delete registry

依赖冲突

问题: 出现版本冲突或依赖不兼容问题。

解决方案:

bash
# 删除node_modules和package-lock.json
rm -rf node_modules package-lock.json

# 重新安装依赖
npm install

# 或使用npm audit修复
npm audit
npm audit fix

# 检查过时的包
npm outdated

# 更新依赖
npm update

Node.js版本管理

问题: Node.js版本不兼容。

解决方案:

bash
# 使用nvm管理Node.js版本
# 安装nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# 安装特定版本
nvm install 18.17.0
nvm use 18.17.0

# 设置默认版本
nvm alias default 18.17.0

# 检查当前版本
node --version

内存和性能问题

内存泄漏

问题: 应用程序内存使用持续增长。

诊断方法:

javascript
// 监控内存使用
function monitorMemory() {
  const usage = process.memoryUsage();
  console.log({
    rss: `${Math.round(usage.rss / 1024 / 1024)} MB`,
    heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)} MB`,
    heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)} MB`,
    external: `${Math.round(usage.external / 1024 / 1024)} MB`
  });
}

// 定期监控内存
setInterval(monitorMemory, 5000);

常见内存泄漏原因及解决方案:

  1. 事件监听器未移除:
javascript
// 问题代码
class EventEmitter {
  constructor() {
    this.data = [];
  }
  
  startListening() {
    process.on('data', this.handleData.bind(this)); // 未移除的监听器
  }
  
  handleData(data) {
    this.data.push(data);
  }
}

// 解决方案
class EventEmitter {
  constructor() {
    this.data = [];
    this.handleDataBound = this.handleData.bind(this);
  }
  
  startListening() {
    process.on('data', this.handleDataBound);
  }
  
  stopListening() {
    process.removeListener('data', this.handleDataBound);
  }
  
  handleData(data) {
    this.data.push(data);
  }
}
  1. 全局变量积累:
javascript
// 问题代码
const cache = new Map(); // 全局缓存未清理

function addToCache(key, value) {
  cache.set(key, value); // 缓存持续增长
}

// 解决方案
const cache = new Map();
const maxCacheSize = 1000;

function addToCache(key, value) {
  if (cache.size >= maxCacheSize) {
    // 移除最旧的条目
    const firstKey = cache.keys().next().value;
    cache.delete(firstKey);
  }
  cache.set(key, value);
}

// 定期清理过期缓存
setInterval(() => {
  // 清理逻辑
}, 300000); // 5分钟

事件循环阻塞

问题: 应用响应缓慢或无响应。

诊断和解决方案:

javascript
// 检测事件循环延迟
function detectEventLoopDelay() {
  const start = Date.now();
  
  setTimeout(() => {
    const delay = Date.now() - start;
    if (delay > 10) { // 如果延迟超过10ms
      console.warn(`事件循环延迟: ${delay}ms`);
    }
  }, 0);
}

// 避免阻塞事件循环
// 不好的做法
function badLongRunningOperation() {
  const arr = new Array(10000000).fill(0);
  for (let i = 0; i < arr.length; i++) {
    arr[i] = i * 2; // 阻塞事件循环
  }
  return arr;
}

// 好的做法 - 分批处理
function goodLongRunningOperation() {
  return new Promise((resolve) => {
    const arr = new Array(10000000).fill(0);
    let i = 0;
    const batchSize = 10000;
    
    function processBatch() {
      const end = Math.min(i + batchSize, arr.length);
      for (; i < end; i++) {
        arr[i] = i * 2;
      }
      
      if (i < arr.length) {
        setImmediate(processBatch); // 让出控制权
      } else {
        resolve(arr);
      }
    }
    
    processBatch();
  });
}

异步编程问题

回调地狱

问题: 嵌套回调导致代码难以维护。

解决方案:

javascript
// 问题代码 - 回调地狱
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getEvenEvenMoreData(c, function(d) {
        console.log(d);
      });
    });
  });
});

// 解决方案1 - 使用Promise
function getDataAsync() {
  return new Promise((resolve, reject) => {
    getData(resolve);
  });
}

function getMoreDataAsync(a) {
  return new Promise((resolve, reject) => {
    getMoreData(a, resolve);
  });
}

// 使用链式调用
getDataAsync()
  .then(a => getMoreDataAsync(a))
  .then(b => getEvenMoreDataAsync(b))
  .then(c => getEvenEvenMoreDataAsync(c))
  .then(d => console.log(d))
  .catch(err => console.error(err));

// 解决方案2 - 使用async/await
async function processAsyncData() {
  try {
    const a = await getDataAsync();
    const b = await getMoreDataAsync(a);
    const c = await getEvenMoreDataAsync(b);
    const d = await getEvenEvenMoreDataAsync(c);
    console.log(d);
  } catch (err) {
    console.error(err);
  }
}

Promise错误处理

问题: Promise错误未正确处理。

解决方案:

javascript
// 问题代码
async function badErrorHandling() {
  const result = await someAsyncFunction(); // 错误未被捕获
  return result;
}

// 解决方案1 - 使用try-catch
async function goodErrorHandling() {
  try {
    const result = await someAsyncFunction();
    return result;
  } catch (error) {
    console.error('异步操作失败:', error);
    throw error; // 或返回默认值
  }
}

// 解决方案2 - 使用Promise链
function handleAsyncWithCatch() {
  return someAsyncFunction()
    .then(result => {
      return result;
    })
    .catch(error => {
      console.error('异步操作失败:', error);
      throw error;
    });
}

网络和服务器问题

端口被占用

问题: 服务器启动失败,提示端口被占用。

解决方案:

bash
# 查看端口占用情况
netstat -tulpn | grep :3000
lsof -i :3000
# Windows: netstat -ano | findstr :3000

# 终止占用端口的进程
kill -9 <PID>  # Linux/Mac
# Windows: taskkill /PID <PID> /F
javascript
// 优雅处理端口占用
const express = require('express');
const app = express();

function startServer(port = 3000) {
  const server = app.listen(port, () => {
    console.log(`服务器运行在端口 ${port}`);
  });

  server.on('error', (err) => {
    if (err.code === 'EADDRINUSE') {
      console.log(`端口 ${port} 被占用,尝试 ${port + 1}`);
      startServer(port + 1);
    } else {
      console.error('服务器启动错误:', err);
    }
  });
}

startServer();

请求超时

问题: HTTP请求经常超时。

解决方案:

javascript
const http = require('http');

// 设置请求超时
function makeRequestWithTimeout(url, timeout = 5000) {
  return new Promise((resolve, reject) => {
    const request = http.get(url, (res) => {
      let data = '';
      
      res.on('data', (chunk) => {
        data += chunk;
      });
      
      res.on('end', () => {
        resolve(data);
      });
    });
    
    request.on('error', reject);
    
    // 设置超时
    request.setTimeout(timeout, () => {
      request.destroy();
      reject(new Error('请求超时'));
    });
  });
}

数据库连接问题

连接池耗尽

问题: 数据库连接数过多,连接池耗尽。

解决方案:

javascript
const mysql = require('mysql2/promise');

// 配置连接池
const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  
  // 连接池配置
  connectionLimit: 10,      // 最大连接数
  queueLimit: 0,           // 等待队列长度(0表示无限制)
  acquireTimeout: 60000,   // 获取连接超时时间
  timeout: 60000,          // 查询超时时间
  reconnect: true          // 自动重连
});

// 正确使用连接
async function queryDatabase(sql, params) {
  let connection;
  try {
    connection = await pool.getConnection();
    const [results] = await connection.execute(sql, params);
    return results;
  } catch (error) {
    console.error('数据库查询错误:', error);
    throw error;
  } finally {
    if (connection) {
      connection.release(); // 重要:释放连接
    }
  }
}

连接泄漏

问题: 数据库连接未正确关闭,导致连接泄漏。

解决方案:

javascript
// 使用连接池管理连接
class DatabaseManager {
  constructor() {
    this.pool = mysql.createPool({
      // 配置...
    });
  }
  
  async executeQuery(sql, params) {
    let connection;
    try {
      connection = await this.pool.getConnection();
      const [results] = await connection.execute(sql, params);
      return results;
    } finally {
      // 确保连接被释放
      if (connection) {
        connection.release();
      }
    }
  }
  
  async executeTransaction(transactionFn) {
    const connection = await this.pool.getConnection();
    
    try {
      await connection.beginTransaction();
      
      const result = await transactionFn(connection);
      
      await connection.commit();
      return result;
    } catch (error) {
      await connection.rollback();
      throw error;
    } finally {
      // 确保事务连接被释放
      connection.release();
    }
  }
}

安全问题

路径遍历攻击

问题: 文件路径可能被操纵以访问受限文件。

解决方案:

javascript
const path = require('path');
const fs = require('fs').promises;

// 安全的文件访问
async function safeFileAccess(requestedPath) {
  // 解析路径
  const resolvedPath = path.resolve(requestedPath);
  const baseDir = path.resolve('public'); // 限制访问目录
  
  // 检查路径是否在允许的目录内
  if (!resolvedPath.startsWith(baseDir)) {
    throw new Error('路径访问被拒绝');
  }
  
  // 读取文件
  const content = await fs.readFile(resolvedPath);
  return content;
}

// Express中间件示例
app.get('/files/:filename', async (req, res) => {
  try {
    const filename = req.params.filename;
    const filePath = path.join('public', filename);
    const content = await safeFileAccess(filePath);
    res.send(content);
  } catch (error) {
    res.status(403).send('访问被拒绝');
  }
});

部署问题

环境变量问题

问题: 生产环境缺少必需的环境变量。

解决方案:

javascript
// 环境变量验证
function validateEnvironment() {
  const requiredEnvVars = [
    'NODE_ENV',
    'PORT',
    'DB_HOST',
    'DB_USER',
    'DB_PASSWORD',
    'JWT_SECRET'
  ];
  
  const missing = requiredEnvVars.filter(envVar => !process.env[envVar]);
  
  if (missing.length > 0) {
    console.error('缺少必需的环境变量:', missing.join(', '));
    process.exit(1);
  }
}

validateEnvironment();

进程管理问题

问题: 应用程序意外退出或无法正确重启。

解决方案:

javascript
// 全局错误处理
process.on('uncaughtException', (error) => {
  console.error('未捕获的异常:', error);
  console.error(error.stack);
  // 在生产环境中,通常需要优雅地关闭应用
  process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('未处理的Promise拒绝:', reason);
  console.error('Promise:', promise);
  // 记录错误,但不退出应用
});

// 信号处理
process.on('SIGTERM', () => {
  console.log('收到SIGTERM信号,正在优雅关闭...');
  // 执行清理操作
  server.close(() => {
    console.log('服务器已关闭');
    process.exit(0);
  });
});

process.on('SIGINT', () => {
  console.log('收到SIGINT信号,正在关闭...');
  process.exit(0);
});

调试技巧

使用Node.js调试器

bash
# 启动调试模式
node --inspect app.js

# 或使用--inspect-brk在第一行暂停
node --inspect-brk app.js

# 远程调试
node --inspect=0.0.0.0:9229 app.js

性能分析

javascript
// 使用内置性能钩子
const { PerformanceObserver, performance } = require('perf_hooks');

const obs = new PerformanceObserver((items) => {
  items.getEntries().forEach((entry) => {
    console.log(`${entry.name}: ${entry.duration}ms`);
  });
});
obs.observe({ entryTypes: ['measure', 'navigation', 'mark'] });

// 性能测量
performance.mark('start-operation');
// 执行操作
performance.mark('end-operation');
performance.measure('operation', 'start-operation', 'end-operation');

日志调试

javascript
// 详细的错误日志
function logError(error, context = {}) {
  console.error({
    timestamp: new Date().toISOString(),
    error: error.message,
    stack: error.stack,
    context: context,
    pid: process.pid,
    memory: process.memoryUsage()
  });
}

// 使用示例
try {
  // 可能出错的代码
} catch (error) {
  logError(error, {
    operation: 'user-login',
    userId: 123,
    ip: '192.168.1.1'
  });
}

通过了解和掌握这些常见问题的解决方案,可以更有效地开发、调试和维护Node.js应用。