Anthony Giniers
@antogyn
@aginiers
Asynchronous context tracking in Node.js
https://slides.com/antogyn/als
Asynchronous context
Context that is kept across asynchronous boundaries
- During the async operation
- After the async operation
async function start() {
const context = { ... };
await callApi(..., context);
console.log(context); // updated
}
async function callApi(..., context) {
...
context.apiCallIsDone = true;
}
Using the stack
π
// we need context in all our code!
console.log(..., { traceId: context.traceId });
Thread Local
Common solution in other languages: Thread-scoped context (Thread Local)
But Node.js is single-threaded π
let requestId;
async function start() {
requestId = randomUUID();
await callApi();
}
async function callApi() {
...
console.log("Call API ok", { requestId });
}
start();
Thread Local
Common solution in other languages: Thread-scoped context (Thread Local)
But Node.js is single-threaded π
let requestId;
async function start() {
requestId = randomUUID();
await callApi();
}
async function callApi() {
...
console.log("Call API ok", { requestId });
}
start();
Thread Local
Common solution in other languages: Thread-scoped context (Thread Local)
But Node.js is single-threaded π
let requestId;
async function start() {
requestId = randomUUID();
await callApi();
}
async function callApi() {
...
console.log("Call API ok", { requestId });
}
start();
Thread Local
Common solution in other languages: Thread-scoped context (Thread Local)
But Node.js is single-threaded π
let requestId;
async function start() {
requestId = randomUUID();
await callApi();
}
async function callApi() {
...
console.log("Call API ok", { requestId });
}
start();
π₯ Breaks with multiple concurrent requests
Continuation Local Storage
Continuation Local Storage
import cls from "continuation-local-storage";
const namespace = cls.createNamespace("request");
async function start() {
namespace.run(async () => {
...
...
});
}
async function callApi() {
...
console.log("Call API ok", { requestId: namespace.get("requestId") });
}
Continuation Local Storage
import cls from "continuation-local-storage";
const namespace = cls.createNamespace("request");
async function start() {
namespace.run(async () => {
...
...
});
}
async function callApi() {
...
console.log("Call API ok", { requestId: namespace.get("requestId") });
}
Continuation Local Storage
import cls from "continuation-local-storage";
const namespace = cls.createNamespace("request");
async function start() {
namespace.run(async () => {
namespace.set("requestId", randomUUID());
await callApi();
});
}
async function callApi() {
...
console.log("Call API ok", { requestId: namespace.get("requestId") });
}
Continuation Local Storage
import cls from "continuation-local-storage";
const namespace = cls.createNamespace("request");
async function start() {
namespace.run(async () => {
namespace.set("requestId", randomUUID());
await callApi();
});
}
async function callApi() {
...
console.log("Call API ok", { requestId: namespace.get("requestId") });
}
Continuation Local Storage
import cls from "continuation-local-storage";
const namespace = cls.createNamespace("request");
async function start() {
namespace.run(async () => {
namespace.set("requestId", randomUUID());
await callApi();
});
}
async function callApi() {
...
console.log("Call API ok", { requestId: namespace.get("requestId") });
}
π
Async Local Storage
Async Local Storage
import { AsyncLocalStorage } from "node:async_hooks";
const requestALS = new AsyncLocalStorage<{ requestId: string, userId?: string }>();
async function start() {
requestALS.run({ requestId: randomUUID() }, async () => {
...
...
...
});
}
async function callApi() {
...
const { requestId, userId } = requestALS.getStore();
console.log("Call API ok", { requestId, userId });
}
Async Local Storage
import { AsyncLocalStorage } from "node:async_hooks";
const requestALS = new AsyncLocalStorage<{ requestId: string, userId?: string }>();
async function start() {
requestALS.run({ requestId: randomUUID() }, async () => {
...
...
...
});
}
async function callApi() {
...
const { requestId, userId } = requestALS.getStore();
console.log("Call API ok", { requestId, userId });
}
Async Local Storage
import { AsyncLocalStorage } from "node:async_hooks";
const requestALS = new AsyncLocalStorage<{ requestId: string, userId?: string }>();
async function start() {
requestALS.run({ requestId: randomUUID() }, async () => {
const requestStore = requestALS.getStore();
...
...
});
}
async function callApi() {
...
const { requestId, userId } = requestALS.getStore();
console.log("Call API ok", { requestId, userId });
}
Async Local Storage
import { AsyncLocalStorage } from "node:async_hooks";
const requestALS = new AsyncLocalStorage<{ requestId: string, userId?: string }>();
async function start() {
requestALS.run({ requestId: randomUUID() }, async () => {
const requestStore = requestALS.getStore();
requestStore.userId = getUserId();
await callApi();
});
}
async function callApi() {
...
const { requestId, userId } = requestALS.getStore();
console.log("Call API ok", { requestId, userId });
}
Async Local Storage
import { AsyncLocalStorage } from "node:async_hooks";
const requestALS = new AsyncLocalStorage<{ requestId: string, userId?: string }>();
async function start() {
requestALS.run({ requestId: randomUUID() }, async () => {
const requestStore = requestALS.getStore();
requestStore.userId = getUserId();
await callApi();
});
}
async function callApi() {
...
const { requestId, userId } = requestALS.getStore();
console.log("Call API ok", { requestId, userId });
}
ALS at Swan
import { initRequestContextValueMiddleware } from "@swan-io/utils";
function initNestjsMiddlewares(app) {
...
app.use(initRequestContextValueMiddleware());
}
ALS at Swan
import { setRequestContextValue, getRequestContextValue } from "@swan-io/utils";
async function start() {
setRequestContextValue<string>("requestId", randomUUID());
await callApi();
}
async function callApi() {
const requestId = getRequestContextValue<string>("requestId");
console.log("Call API ok", { requestId });
}
ALS at Swan
import { setRequestContextValue, getRequestContextValue } from "@swan-io/utils";
async function start() {
setRequestContextValue<string>("requestId", randomUUID());
await callApi();
}
async function callApi() {
const requestId = getRequestContextValue<string>("requestId");
console.log("Call API ok", { requestId });
}
ALS at Swan
import { setRequestContextValue, getRequestContextValue } from "@swan-io/utils";
async function start() {
setRequestContextValue<string>("requestId", randomUUID());
await callApi();
}
async function callApi() {
const requestId = getRequestContextValue<string>("requestId");
console.log("Call API ok", { requestId });
}
ALS at Swan
import { setRequestContextValue, getRequestContextValue } from "@swan-io/utils";
async function start() {
setRequestContextValue<string>("requestId", randomUUID());
await callApi();
}
async function callApi() {
const requestId = getRequestContextValue<string>("requestId");
console.log("Call API ok", { requestId });
}
- Not type safe
-
requestId
Β is repeated
ALS at Swan
import { initRequestContextValueHandler } from "@swan-io/utils";
const requestIdContext = initRequestContextValueHandler<string>("requestId");
async function start() {
requestIdContext.set(randomUUID());
await callApi();
}
async function callApi() {
const requestId = requestIdContext.get();
console.log("Call API ok", { requestId });
}
ALS at Swan
import { initRequestContextValueHandler } from "@swan-io/utils";
const requestIdContext = initRequestContextValueHandler<string>("requestId");
async function start() {
requestIdContext.set(randomUUID());
await callApi();
}
async function callApi() {
const requestId = requestIdContext.get();
console.log("Call API ok", { requestId });
}
ALS at Swan
import { initRequestContextValueHandler } from "@swan-io/utils";
const requestIdContext = initRequestContextValueHandler<string>("requestId");
async function start() {
requestIdContext.set(randomUUID());
await callApi();
}
async function callApi() {
const requestId = requestIdContext.get();
console.log("Call API ok", { requestId });
}
ALS at Swan
import { initRequestContextValueHandler } from "@swan-io/utils";
const requestIdContext = initRequestContextValueHandler<string>("requestId");
async function start() {
requestIdContext.set(randomUUID());
await callApi();
}
async function callApi() {
const requestId = requestIdContext.get();
console.log("Call API ok", { requestId });
}
β π―
Thank you!
Questions ?
Asynchronous context tracking in Node.js (@ Swan)
By antogyn
Asynchronous context tracking in Node.js (@ Swan)
- 83