Skip to content

nodejs笔记

新特性

HTTP请求超时自动取消

javascript
async function fetchData(url) {
  try {
    const response = await fetch(url, {
      signal: AbortSignal.timeout(5000) // Built-in timeout support
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    if (error.name === 'TimeoutError') {
      throw new Error('Request timed out');
    }
    throw error;
  }
}

HTTP请求手动取消

javascript
// Cancel long-running operations cleanly
const controller = new AbortController();

// Set up automatic cancellation
setTimeout(() => controller.abort(), 10000);

try {
  const data = await fetch('https://slow-api.com/data', {
    signal: controller.signal
  });
  console.log('Data received:', data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Request was cancelled - this is expected behavior');
  } else {
    console.error('Unexpected error:', error);
  }
}

内置的测试框架

javascript
// test/math.test.js
import { test, describe } from 'node:test';
import assert from 'node:assert';
import { add, multiply } from '../math.js';

describe('Math functions', () => {
  test('adds numbers correctly', () => {
    assert.strictEqual(add(2, 3), 5);
  });

  test('handles async operations', async () => {
    const result = await multiply(2, 3);
    assert.strictEqual(result, 6);
  });

  test('throws on invalid input', () => {
    assert.throws(() => add('a', 'b'), /Invalid input/);
  });
});

运行测试用例的命令:

shell
# Run all tests with built-in runner
node --test

# Watch mode for development
node --test --watch

# Coverage reporting (Node.js 20+)
node --test --experimental-test-coverage

监听模式和环境变量管理

json
{
  "name": "modern-node-app",
  "type": "module",
  "engines": {
    "node": ">=20.0.0"
  },
  "scripts": {
    "dev": "node --watch --env-file=.env app.js",
    "test": "node --test --watch",
    "start": "node app.js"
  }
}

内置安全和性能监控模块

shell
# Run with restricted file system access
node --experimental-permission --allow-fs-read=./data --allow-fs-write=./logs app.js

# Network restrictions
node --experimental-permission --allow-net=api.example.com app.js
# Above allow-net feature not avaiable yet, PR merged in node.js repo, will be available in future release
javascript
import { PerformanceObserver, performance } from 'node:perf_hooks';

// Set up automatic performance monitoring
const obs = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 100) { // Log slow operations
      console.log(`Slow operation detected: ${entry.name} took ${entry.duration}ms`);
    }
  }
});
obs.observe({ entryTypes: ['function', 'http', 'dns'] });

// Instrument your own operations
async function processLargeDataset(data) {
  performance.mark('processing-start');

  const result = await heavyProcessing(data);

  performance.mark('processing-end');
  performance.measure('data-processing', 'processing-start', 'processing-end');

  return result;
}

分发和部署

可以打包成一个可执行文件:

shell
# Create a self-contained executable
node --experimental-sea-config sea-config.json

sea-config.json 内容:

json
{{
  "main": "app.js",
  "output": "my-app-bundle.blob",
  "disableExperimentalSEAWarning": true,
  "assets": [
    {
      "glob": "**/*",
      "cwd": "./"
    }
  ]
}

导入地图和内部依赖解析

package.json文件中:

json
{
  "type": "module",
  "imports": {
    "#config": "./src/config/index.js",
    "#utils/*": "./src/utils/*.js",
    "#db": "./src/database/connection.js"
  }
}

支持动态导入:

javascript
// Clean internal imports that don't break when you reorganize
import config from '#config';
import { logger, validator } from '#utils/common';
import db from '#db';

// Load features based on configuration or environment
async function loadDatabaseAdapter() {
  const dbType = process.env.DATABASE_TYPE || 'sqlite';

  try {
    const adapter = await import(`#db/adapters/${dbType}`);
    return adapter.default;
  } catch (error) {
    console.warn(`Database adapter ${dbType} not available, falling back to sqlite`);
    const fallback = await import('#db/adapters/sqlite');
    return fallback.default;
  }
}

// Conditional feature loading
async function loadOptionalFeatures() {
  const features = [];

  if (process.env.ENABLE_ANALYTICS === 'true') {
    const analytics = await import('#features/analytics');
    features.push(analytics.default);
  }

  if (process.env.ENABLE_MONITORING === 'true') {
    const monitoring = await import('#features/monitoring');
    features.push(monitoring.default);
  }

  return features;
}

参考链接: Modern Node.js Patterns for 2025

常见问题

###依赖安装失败

nodejs 在 17及以上版本安装依赖失败,可尝试加上如下配置:

javascript
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

.npmrc 文件中加上如下配置:

shell
node-options=--openssl-legacy-provider

常用技巧

设置项目根目录

ESM项目

ESM 项目中,可在根目录所在的 index.mjs 中使用如下代码设置项目根目录:

javascript
process.env.ROOT_DIR = path.dirname(fileURLToPath(import.meta.url));

import.meta.url 使用如下:

javascript
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const IMAGE_FOLDER_PATH = './images';

console.log(import.meta.url);
// file:///Users/alice/test.mjs
console.log(fileURLToPath(import.meta.url));
// /Users/alice/test.mjs
console.log(path.dirname(fileURLToPath(import.meta.url)));
// /Users/alice
console.log(path.resolve(process.env.ROOT_DIR, IMAGE_FOLDER_PATH, 'abbc.jpg'));
// /Users/alice/images/abbc.jpg

commonjs项目

javascript
process.env.ROOT_DIR = __dirname;