Работали ли вы с библиотеками управления состоянием в Node.js?

«Работали ли вы с библиотеками управления состоянием в Node.js?» — вопрос из категории JavaScript, который задают на 26% собеседований Node.js Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Node.js контексте управление состоянием имеет свою специфику. Вот подходы, которые я использовал:

1. EventEmitter для реактивного состояния:

const EventEmitter = require('events');

class ApplicationState extends EventEmitter {
  constructor() {
    super();
    this._state = {
      connectedUsers: 0,
      serverStatus: 'running',
      lastError: null
    };
  }

  setState(updates) {
    this._state = { ...this._state, ...updates };
    this.emit('stateChanged', this._state);
  }

  getState() {
    return { ...this._state };
  }
}

// Использование
const appState = new ApplicationState();
appState.on('stateChanged', (newState) => {
  console.log('State updated:', newState);
  // Обновляем метрики, логируем и т.д.
});

appState.setState({ connectedUsers: 42 });

2. Finite State Machines (XState) для сложной логики:

const { Machine, interpret } = require('xstate');

const paymentMachine = Machine({
  id: 'payment',
  initial: 'idle',
  states: {
    idle: {
      on: { START: 'processing' }
    },
    processing: {
      on: {
        SUCCESS: 'completed',
        FAILURE: 'failed'
      }
    },
    completed: { type: 'final' },
    failed: {
      on: { RETRY: 'processing' }
    }
  }
});

const paymentService = interpret(paymentMachine)
  .onTransition((state) => {
    console.log('Payment state:', state.value);
  })
  .start();

paymentService.send('START');

3. Shared memory для worker процессов: При использовании кластеризации или worker_threads можно использовать:

  • SharedArrayBuffer для примитивных данных
  • Redis как распределенное хранилище состояния
  • Базы данных (PostgreSQL, MongoDB) для персистентного состояния

4. Контекст выполнения (Execution Context): Для передачи состояния через middleware цепочку в Express:

// Установка контекста
app.use((req, res, next) => {
  req.context = {
    requestId: uuid(),
    userId: req.user?.id,
    startTime: Date.now()
  };
  next();
});

// Использование в обработчике
app.get('/api/data', async (req, res) => {
  const data = await db.query('SELECT * FROM data', {
    requestId: req.context.requestId
  });
  res.json(data);
});

Выбор подхода зависит от требований: для простых случаев достаточно EventEmitter, для сложных бизнес-процессов — конечные автоматы, для распределенных систем — Redis.