Модуль process — управление процессом

Зачем нужно

process -- глобальный объект Node.js, дающий доступ к текущему процессу: переменные окружения, аргументы командной строки, стандартные потоки ввода/вывода, управление завершением и обработка системных сигналов. Это интерфейс между вашим кодом и операционной системой.

Где используется

  • Чтение переменных окружения (DATABASE_URL, NODE_ENV, PORT)
  • Парсинг аргументов CLI-инструментов
  • Graceful shutdown серверов (SIGTERM, SIGINT)
  • Логирование в stdout/stderr
  • Глобальная обработка необработанных ошибок
  • Мониторинг использования памяти

Предпосылки


Импорт

// process — глобальный, импорт не обязателен
console.log(process.version);

// Но можно импортировать явно (ESM)
import process from 'process';

// Или в CommonJS
const process = require('process');

process.env — переменные окружения

// Чтение переменных окружения
console.log(process.env.NODE_ENV);      // 'production', 'development'
console.log(process.env.PORT);          // '3000' (всегда строка!)
console.log(process.env.DATABASE_URL);  // 'postgres://...'
console.log(process.env.HOME);          // '/home/user'
console.log(process.env.PATH);          // '/usr/local/bin:...'

// Задание переменных при запуске
// PORT=8080 NODE_ENV=production node app.js

// ⚠️ process.env значения — ВСЕГДА строки
const port = process.env.PORT;
console.log(typeof port); // 'string'
const portNum = parseInt(process.env.PORT, 10) || 3000;

// Можно устанавливать в рантайме (только для текущего процесса)
process.env.MY_VAR = 'value';

Паттерн конфигурации

// config.js
const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  host: process.env.HOST || 'localhost',
  nodeEnv: process.env.NODE_ENV || 'development',
  db: {
    url: process.env.DATABASE_URL || 'postgres://localhost:5432/mydb',
  },
  isDev: process.env.NODE_ENV !== 'production',
  isProd: process.env.NODE_ENV === 'production',
};

module.exports = config;

// app.js
const config = require('./config');
server.listen(config.port, config.host);

.env файлы с dotenv

# .env (не коммитить в git!)
PORT=3000
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
JWT_SECRET=super-secret-key
NODE_ENV=development
// Установка: npm install dotenv
require('dotenv').config; // загружает .env в process.env

// Или в Node.js 20.6+:
// node --env-file=.env app.js

process.argv — аргументы командной строки

// node app.js --port 8080 --verbose

console.log(process.argv);
// [
//   '/usr/local/bin/node',     // [0] — путь к node
//   '/home/user/app.js',       // [1] — путь к скрипту
//   '--port',                  // [2] — первый аргумент
//   '8080',                    // [3]
//   '--verbose'                // [4]
// ]

// Аргументы пользователя начинаются с [2]
const args = process.argv.slice(2);
console.log(args); // ['--port', '8080', '--verbose']

Простой парсинг аргументов

// node app.js --port 8080 --host localhost --verbose

function parseArgs(args) {
  const result = {};
  for (let i = 0; i < args.length; i++) {
    if (args[i].startsWith('--')) {
      const key = args[i].slice(2);
      const next = args[i + 1];
      if (next && !next.startsWith('--')) {
        result[key] = next;
        i++; // пропустить значение
      } else {
        result[key] = true; // флаг без значения
      }
    }
  }
  return result;
}

const options = parseArgs(process.argv.slice(2));
// { port: '8080', host: 'localhost', verbose: true }

parseArgs (Node.js 18.3+)

const { parseArgs } = require('util');

// node app.js --port 8080 --verbose --name myapp
const { values } = parseArgs({
  options: {
    port: { type: 'string', short: 'p', default: '3000' },
    verbose: { type: 'boolean', short: 'v', default: false },
    name: { type: 'string', short: 'n' },
  },
  strict: true,
  args: process.argv.slice(2),
});

console.log(values);
// { port: '8080', verbose: true, name: 'myapp' }

Стандартные потоки: stdin, stdout, stderr

// stdout — обычный вывод (Writable Stream)
process.stdout.write('Hello ');
process.stdout.write('World\n');
// Эквивалент: console.log('Hello World')

// stderr — вывод ошибок (Writable Stream)
process.stderr.write('ERROR: something went wrong\n');
// Эквивалент: console.error('ERROR: something went wrong')

// Разница: stdout и stderr — разные потоки
// stdout можно перенаправить в файл, а stderr оставить в терминале:
// node app.js > output.log 2> errors.log
// node app.js > output.log     (stderr по-прежнему в терминале)

stdin — чтение ввода

// Чтение из stdin (Readable Stream)
process.stdin.setEncoding('utf-8');

process.stdin.on('data', (input) => {
  const trimmed = input.trim();
  if (trimmed === 'exit') {
    process.exit(0);
  }
  console.log(`Вы ввели: ${trimmed}`);
});

console.log('Введите текст (exit для выхода):');

Pipe через stdin/stdout

# Использование в pipe-цепочке
echo "hello world" | node uppercase.js
cat data.txt | node process.js > result.txt
// uppercase.js — читает stdin, пишет в stdout верхний регистр
process.stdin.setEncoding('utf-8');

let data = '';
process.stdin.on('data', (chunk) => data += chunk);
process.stdin.on('end', () => {
  process.stdout.write(data.toUpperCase());
});

process.exit — завершение процесса

// Коды выхода
process.exit(0);   // Успех (по умолчанию)
process.exit(1);   // Общая ошибка

// Стандартные коды:
// 0   — успех
// 1   — общая ошибка
// 2   — некорректное использование (неправильные аргументы)
// 126 — команда не может быть выполнена
// 127 — команда не найдена
// 130 — прерван по Ctrl+C (128 + SIGINT=2)

// ❌ Не рекомендуется вызывать process.exit напрямую
// Это обрывает незавершённые операции (запись в файл, ответ клиенту)

// ✅ Лучше: установить exitCode и дать процессу завершиться естественно
process.exitCode = 1; // Процесс завершится с кодом 1 когда Event Loop опустеет

// Чтение кода после завершения:
// echo $?     (Linux/macOS)
// echo %ERRORLEVEL%  (Windows)

process.cwd и другие свойства

// Текущая рабочая директория
console.log(process.cwd);
// '/home/user/project'
// (откуда запущен node, не где лежит файл)

// Изменить рабочую директорию
process.chdir('/tmp');

// Информация о процессе
console.log(process.pid);          // 12345 (ID процесса)
console.log(process.ppid);         // 12340 (ID родительского процесса)
console.log(process.title);        // 'node'
console.log(process.version);      // 'v22.12.0'
console.log(process.versions.v8);  // '12.4.254.21'
console.log(process.platform);     // 'linux', 'darwin', 'win32'
console.log(process.arch);         // 'x64', 'arm64'

// Использование памяти
const mem = process.memoryUsage;
console.log({
  rss: `${Math.round(mem.rss / 1024 / 1024)} MB`,       // Resident Set Size
  heapTotal: `${Math.round(mem.heapTotal / 1024 / 1024)} MB`,
  heapUsed: `${Math.round(mem.heapUsed / 1024 / 1024)} MB`,
  external: `${Math.round(mem.external / 1024 / 1024)} MB`,
});
// { rss: '35 MB', heapTotal: '6 MB', heapUsed: '4 MB', external: '1 MB' }

// Время работы процесса (в секундах)
console.log(process.uptime); // 125.432

// Точное время (наносекунды) — для замеров производительности
const start = process.hrtime.bigint;
// ... тяжёлая операция ...
const end = process.hrtime.bigint;
console.log(`Заняло ${Number(end - start) / 1e6} мс`);

Системные сигналы

// SIGINT (Ctrl+C) — прерывание
process.on('SIGINT', () => {
  console.log('\nПолучен SIGINT (Ctrl+C)');
  cleanup;
  process.exit(0);
});

// SIGTERM — запрос на завершение (Docker, systemd, PM2)
process.on('SIGTERM', () => {
  console.log('Получен SIGTERM');
  gracefulShutdown;
});

// Graceful shutdown паттерн
const http = require('http');
const server = http.createServer((req, res) => res.end('OK'));

function gracefulShutdown() {
  console.log('Начинаем graceful shutdown...');

  // 1. Перестаём принимать новые соединения
  server.close(() => {
    console.log('HTTP-сервер закрыт');

    // 2. Закрываем соединения с БД, очереди и т.д.
    // db.close();
    // redis.quit;

    console.log('Все ресурсы освобождены');
    process.exit(0);
  });

  // 3. Таймаут на случай зависших соединений
  setTimeout(() => {
    console.error('Принудительное завершение (таймаут)');
    process.exit(1);
  }, 10000);
}

process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

server.listen(3000);

Глобальная обработка ошибок

// uncaughtException — необработанное исключение
process.on('uncaughtException', (err) => {
  console.error('UNCAUGHT EXCEPTION:', err.message);
  console.error(err.stack);

  // ⚠️ После uncaughtException процесс в неопределённом состоянии
  // НУЖНО завершить процесс после логирования
  process.exit(1);
});

// unhandledRejection — необработанный Promise rejection
process.on('unhandledRejection', (reason, promise) => {
  console.error('UNHANDLED REJECTION:', reason);

  // В Node.js 15+ — это крашит процесс по умолчанию
  // В более ранних версиях — только warning
});

// warning — предупреждения (deprecation, experimental и т.д.)
process.on('warning', (warning) => {
  console.warn('WARNING:', warning.name, warning.message);
});

// ❌ Антипаттерн: ловить uncaughtException и продолжать работу
// Состояние приложения может быть повреждено
// ✅ Правильно: залогировать → завершить → перезапустить (PM2, Docker)

process.nextTick

// Выполняется ПЕРЕД следующей фазой Event Loop (см. Event Loop)
console.log('start');

process.nextTick(() => {
  console.log('nextTick');
});

console.log('end');

// start
// end
// nextTick

// Применение: гарантировать асинхронный вызов callback-а
function readData(callback) {
  if (cache.has(key)) {
    // ❌ Синхронный вызов — нарушает контракт
    // callback(null, cache.get(key));

    // ✅ Асинхронный вызов — поведение всегда одинаковое
    process.nextTick( => callback(null, cache.get(key)));
  } else {
    db.get(key, callback);
  }
}

Частые ошибки

  1. process.env.PORT без parseInt — значение всегда строка, '3000' + 1 === '30001'
  2. process.exit без закрытия ресурсов — обрывает запись в файл, ответ клиенту, соединения с БД
  3. Игнорирование SIGTERM — Docker/K8s посылают SIGTERM перед kill, без обработчика данные теряются
  4. Продолжение после uncaughtException — процесс в неопределённом состоянии, нужно перезапускать
  5. Путают process.cwd и __dirnamecwd зависит от того откуда запущен node, __dirname — от расположения файла
  6. Коммитят .env в git — пароли и ключи утекают в репозиторий

Практика

  1. Написать скрипт, читающий --port и --host из аргументов и запускающий HTTP-сервер
  2. Реализовать graceful shutdown: SIGTERM → закрыть сервер → выйти
  3. Создать CLI-утилиту, читающую stdin и выводящую статистику (слова, строки, символы)
  4. Настроить конфигурацию через process.env + dotenv с дефолтными значениями
  5. Добавить мониторинг: выводить memoryUsage каждые 10 секунд

Связанные темы

  • Event Loop в Node — process.nextTick и Event Loop
  • fs — process.cwd для разрешения путей
  • path — __dirname vs process.cwd
  • package.json — npm scripts и переменные окружения

Ресурсы


🎓 Источник: Консоль и командная строка в JavaScript и Node.js

  • 📅 2018-10-25 · YouTube · [Marp](../../../Documents/TimurShemsedinov/2018-10-25 — Консоль и командная строка в JavaScript и Node.js (5aSZyKi5BmE).md)
  • Тезисы:
    • process доступен глобально (без require)
    • process.argv[0] = node, [1] = path к скрипту, [2..] = args
    • process.env — переменные окружения; читать ТОЛЬКО при старте, не на каждый запрос
    • process.cwd — текущий каталог откуда запущен процесс (≠ __dirname)
    • process.exit(code) — код возврата важен для CI (0 = OK, ≠ 0 = ошибка)
    • process.stdin, process.stdout, process.stderr — стримы

🎓 Источник: Обзор встроенного Node.js API

  • 📅 2018-09-26 · YouTube · [Marp](../../../Documents/TimurShemsedinov/2018-09-26 — Обзор встроенного Node.js API (sOkjR-N6IAs).md)
  • Тезисы:
    • process.nextTick — выполнить callback СРАЗУ после текущей операции, до event loop
    • process.versions — версии Node, V8, OpenSSL, libuv, ICU — отладка
    • process.memoryUsagerss, heapTotal, heapUsed, external
    • process.exit с кодом ошибки для CI