Skip to main content

Async Context

On Node.js, Keq fully preserves the caller's AsyncLocalStorage (ALS) context: middleware always reads the context the request was started in.

This matters most when requests have to queue. Whether it is serial / concurrent flow control or @keq-request/cache serializing requests that share a cache key, a request still reads its own context by the time it runs.

import { AsyncLocalStorage } from "node:async_hooks";
import { request } from "keq";

const als = new AsyncLocalStorage<{ traceId: string }>();

request.use(async (ctx, next) => {
  // Always this request's own traceId, however long it waited in the queue
  ctx.set("x-trace-id", als.getStore()!.traceId);
  await next();
});

await Promise.all(
  tasks.map((task) =>
    als.run({ traceId: task.traceId }, () =>
      request.get("/api/cats").flowControl("serial", "cats"),
    ),
  ),
);
The context belongs to the await site

Requests are lazy — they only run when awaited (or via .then() / .fire()). What takes effect is the context around the await, not the place where request.get() was written:

const keq = request.get("/api/cats"); // no request is sent here

await als.run({ traceId: "abc" }, () => keq); // reads traceId: "abc"