Как передать requestId через цепочку асинхронных вызовов, не прокидывая его в каждый аргумент? Классическая проблема в Node.js сервисах: middleware логирует requestId, сервисный слой его теряет. Прокидывать параметр через 5 уровней вложенности больно и ломает сигнатуры API. Многие тянут контекст через глобальные переменные или мутят DI-контейнеры, хотя есть встроенное решение.
Как это работает
AsyncLocalStorage из
node:async_hooks создаёт изолированное хранилище, автоматически привязанное к текущему асинхронному контексту (цепочке Promise, таймеров, потоков). В production это позволяет убрать boilerplate:import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';
const storage = new AsyncLocalStorage<{ requestId: string }>();
app.use((req, res, next) => {
storage.run(
{ requestId: req.headers['x-request-id'] || randomUUID() },
() => next()
);
});
function getRequestId(): string {
const store = storage.getStore();
if (!store) throw new Error('No async context');
return store.requestId;
}
async function processPayment(orderId: string) {
logger.info(Processing order ${orderId}, { requestId: getRequestId() });
}Автоматический trace-контекст для логов
Для Pino или Winston можно подключить
mixin — каждое сообщение лога автоматически получит requestId без ручного проброса:const logger = pino({
mixin() {
const store = storage.getStore();
return store ? { requestId: store.requestId } : {};
}
});Подводные камни
У решения есть границы:
- Не работает с коллбэками вне цепочки событий. Если Promise резолвится после завершения контекста — хранилище потеряно.
- Микротаски и
nextTick работают корректно, а Worker Threads — нет. Контекст не наследуется, нужно пробрасывать вручную.- Типичная ошибка: забывают обернуть асинхронный обработчик в
storage.run(), и getStore возвращает undefined в runtime.Вывод: AsyncLocalStorage — элегантная альтернатива DI-контейнерам для сквозного контекста в Node.js, которая делает код чище, тесты проще и интегрируется с OpenTelemetry для production-grade distributed tracing.