Как работает `child_process` в Node.js?

MiddleNode.js · Backend·Обновлено 3 августа 2026
Коротко
Модуль child_process позволяет запускать дочерние процессы операционной системы из Node.js — выполнять команды оболочки, другие Node.js-скрипты или любые исполняемые файлы, взаимодействуя с ними через stdin/stdout/stderr и IPC-канал.

Зачем нужен child_process

Node.js работает в однопоточном событийном цикле, что делает его неэффективным для CPU-интенсивных задач. Модуль child_process решает эту проблему, позволяя запускать отдельные процессы ОС и взаимодействовать с ними асинхронно.

Основные методы

exec и execSync

Запускает команду в оболочке (shell) и буферизует весь вывод. Удобен для коротких команд, но небезопасен при подстановке пользовательских данных из-за shell injection.

import { exec } from 'child_process';

// Асинхронный вариант с callback
exec('ls -la', (error, stdout, stderr) => {
  if (error) {
    console.error('Ошибка:', error.message);
    return;
  }
  console.log('Вывод:', stdout);
});

spawn

Запускает процесс напрямую без оболочки, передавая аргументы массивом. Возвращает потоки (streams) вместо буферизованного вывода — подходит для долгоживущих процессов и больших объёмов данных.

import { spawn } from 'child_process';

const child = spawn('node', ['worker.js']);

// Читаем вывод по мере поступления
child.stdout.on('data', (data) => {
  console.log('stdout:', data.toString());
});

child.stderr.on('data', (data) => {
  console.error('stderr:', data.toString());
});

child.on('close', (code) => {
  console.log(`Процесс завершился с кодом: ${code}`);
});

execFile

Аналог exec, но запускает файл напрямую без оболочки — безопаснее для внешних программ.

fork

Специализированный вариант spawn для дочерних Node.js-процессов. Автоматически создаёт IPC-канал, через который родительский и дочерний процессы могут обмениваться сообщениями через process.send() и событие message.

import { fork } from 'child_process';

// Родительский процесс
const child = fork('./worker.js');

child.send({ task: 'heavyComputation', data: [1, 2, 3] });

child.on('message', (result) => {
  console.log('Результат от воркера:', result);
});
// worker.js — дочерний процесс
process.on('message', (msg) => {
  const result = msg.data.reduce((a: number, b: number) => a + b, 0);
  process.send({ result });
});

Коммуникация между процессами

  • stdin/stdout/stderr — стандартные потоки, доступны у любого дочернего процесса
  • IPC-канал — только при fork() или при опции { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] } в spawn()
  • Данные через IPC передаются как JSON, поэтому сложные объекты сериализуются автоматически

Опции и управление

Все методы принимают объект опций: cwd (рабочая директория), env (переменные окружения), timeout (таймаут выполнения), maxBuffer (лимит буфера для exec). Дочерний процесс можно завершить вызовом child.kill('SIGTERM').

exec vs spawn — когда что использовать

Метод Shell Вывод Когда использовать
exec Да Буфер Короткие команды, результат целиком
spawn Нет Stream Долгие процессы, большой вывод
fork Нет IPC Node.js воркеры с двусторонней связью

Что хочет услышать интервьюер

Знание четырёх основных методов (exec, execFile, spawn, fork) и их принципиальных отличий

Понимание разницы между буферизованным выводом (exec) и потоковым (spawn) и когда каждый применять

Объяснение IPC-канала и того, как fork отличается от spawn для Node.js-воркеров

Осознание рисков безопасности при использовании exec с пользовательскими данными (shell injection)

Понимание, что child_process решает проблему CPU-bound задач в однопоточном Node.js

Пример: exec, spawn и fork — сравнение подходов

import { exec, spawn, fork } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

// exec — короткая команда, получаем результат целиком
async function getNodeVersion(): Promise<string> {
  const { stdout } = await execAsync('node --version');
  return stdout.trim();
}

// spawn — потоковый вывод, безопаснее для аргументов
function streamLogs(filePath: string): void {
  // Аргументы передаются массивом — нет риска shell injection
  const tail = spawn('tail', ['-f', filePath]);

  tail.stdout.on('data', (chunk) => {
    process.stdout.write(chunk);
  });

  tail.on('error', (err) => {
    console.error('Не удалось запустить tail:', err.message);
  });

  // Завершаем дочерний процесс вместе с родительским
  process.on('exit', () => tail.kill('SIGTERM'));
}

// fork — двусторонняя коммуникация с Node.js воркером
function runHeavyTask(data: number[]): Promise<number> {
  return new Promise((resolve, reject) => {
    const worker = fork('./heavy-worker.js');

    worker.send({ data });

    worker.on('message', (msg: { result: number }) => {
      resolve(msg.result);
      worker.kill();
    });

    worker.on('error', reject);

    // Защита от зависания воркера
    setTimeout(() => {
      worker.kill('SIGKILL');
      reject(new Error('Воркер завис — таймаут'));
    }, 5000);
  });
}

Типичные ошибки

Путают exec и spawn: используют exec для долгоживущих процессов, получая переполнение maxBuffer

Не обрабатывают событие 'error' и 'close', теряя информацию об ошибках дочернего процесса

Передают пользовательский ввод напрямую в exec-команду строкой, создавая уязвимость shell injection

Не понимают, что fork создаёт IPC-канал автоматически, и пытаются его настроить вручную через spawn

Забывают завершать дочерние процессы через child.kill(), создавая утечки процессов

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее