← Назад к вопросам
В каких случаях лучше выбрать микросервисы, а в каких монолит для 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_hooks | AsyncLocalStorage |
|---|---|---|
| Уровень | Низкоуровневый | Высокоуровневый |
| Сложность | Сложный | Простой |
| Использование | Отладка, профилирование | Хранение контекста |
| 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 доступна везде
});
}