← Назад к вопросам

В каких случаях лучше выбрать микросервисы, а в каких монолит для Node.js проекта?

3.0 Senior🔥 231 комментариев
#Node.js и JavaScript#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как связаны node:async_hooks и AsyncLocalStorage?

AsyncLocalStorage и async_hooks — это механизмы отслеживания асинхронного контекста в Node.js. AsyncLocalStorage использует async_hooks под капотом для сохранения данных между асинхронными операциями.

Что такое async_hooks?

async_hooks — это низкоуровневый API для отслеживания жизненного цикла асинхронных операций:

const async_hooks = require('async_hooks');

const hook = async_hooks.createHook({
  init(asyncId, type, triggerAsyncId) {
    console.log(`Init: ${type} (${asyncId})`);
  },
  before(asyncId) {
    console.log(`Before: ${asyncId}`);
  },
  after(asyncId) {
    console.log(`After: ${asyncId}`);
  },
  destroy(asyncId) {
    console.log(`Destroy: ${asyncId}`);
  }
});

hook.enable();

// setTimeout создаст async resource
setTimeout(() => {
  console.log('Callback');
}, 100);

// Output:
// Init: Timeout (2)
// Before: 2
// Callback
// After: 2
// Destroy: 2

Что такое AsyncLocalStorage?

AsyncLocalStorage — это высокоуровневый API для хранения данных, привязанных к асинхронному контексту:

import { AsyncLocalStorage } from 'async_hooks';

const userStorage = new AsyncLocalStorage<{ userId: string }>();

// Установить значение для текущего контекста
userStorage.run({ userId: 'user123' }, async () => {
  // Данные доступны во всех асинхронных операциях в этом контексте
  const userData = userStorage.getStore();
  console.log(userData); // { userId: 'user123' }

  await someAsyncOperation();
  // userData всё ещё доступна!
});

Как AsyncLocalStorage использует async_hooks?

// Упрощённая реализация AsyncLocalStorage
class SimplifiedAsyncLocalStorage {
  private stack: Map<number, any> = new Map();
  private hook: any;
  private value: any;

  constructor() {
    const asyncHooks = require('async_hooks');
    this.hook = asyncHooks.createHook({
      init: (asyncId, type, triggerAsyncId) => {
        // Копировать значение из родительского контекста
        if (this.stack.has(triggerAsyncId)) {
          this.stack.set(asyncId, this.stack.get(triggerAsyncId));
        }
      },
      destroy: (asyncId) => {
        // Очистить при завершении
        this.stack.delete(asyncId);
      }
    });
    this.hook.enable();
  }

  run<T>(value: any, fn: () => T): T {
    const asyncId = require('async_hooks').executionAsyncId();
    this.stack.set(asyncId, value);
    return fn();
  }

  getStore() {
    const asyncId = require('async_hooks').executionAsyncId();
    return this.stack.get(asyncId);
  }
}

Практический пример: Request Context в Express

import express from 'express';
import { AsyncLocalStorage } from 'async_hooks';

interface RequestContext {
  requestId: string;
  userId?: string;
  startTime: number;
}

const requestContextStorage = new AsyncLocalStorage<RequestContext>();

const app = express();

// Middleware для создания контекста
app.use((req, res, next) => {
  const context: RequestContext = {
    requestId: req.headers['x-request-id'] || crypto.randomUUID(),
    startTime: Date.now()
  };

  requestContextStorage.run(context, () => {
    next();
  });
});

// Middleware для аутентификации
app.use((req, res, next) => {
  const context = requestContextStorage.getStore();
  // Обновляем userId в контексте
  if (context) {
    context.userId = 'user123';
  }
  next();
});

// Пример endpoint'а
app.get('/users/:id', async (req, res) => {
  const context = requestContextStorage.getStore();
  
  // Данные контекста доступны везде!
  console.log(`[${context.requestId}] Fetching user ${req.params.id}`);
  
  const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
  
  console.log(`[${context.requestId}] User fetched`);
  
  res.json(user);
});

// Глобальный logger который использует контекст
function log(message: string) {
  const context = requestContextStorage.getStore();
  if (context) {
    console.log(`[${context.requestId}] ${message}`);
  } else {
    console.log(message);
  }
}

Ключевые различия

Параметрasync_hooksAsyncLocalStorage
УровеньНизкоуровневыйВысокоуровневый
СложностьСложныйПростой
ИспользованиеОтладка, профилированиеХранение контекста
PerformanceМедленнееБыстрее

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

// ✗ ПЛОХО: Потеря контекста при параллельных операциях
const storage = new AsyncLocalStorage<string>();

storage.run('value1', async () => {
  const promises = [
    new Promise(r => setTimeout(r, 100)),
    new Promise(r => setTimeout(r, 100))
  ];
  
  await Promise.all(promises);
  console.log(storage.getStore()); // 'value1' - всё в порядке
});

// ✓ ХОРОШО: Правильное управление контекстом
async function handleRequest(requestId: string) {
  return requestContextStorage.run({ requestId }, async () => {
    await db.query(...);
    await sendEmail(...);
    // requestId доступна везде
  });
}