Async Local Storage

async_hooks AsyncLocalStorage — request-scoped context that propagates across `await`. The store lives in the coroutine task struct, so a value set with run(...) survives an await/park/resume — the classic use is carrying a request id (or a "current user") through an async call tree without threading it explicitly through every function's parameters.

import { AsyncLocalStorage } from 'async_hooks'

interface RequestCtx {
  id: number
}

const store = new AsyncLocalStorage<RequestCtx>()

async function loadUser(): Promise<void> {
  // getStore() here reads the id set by the run(...) far up the call tree,
  // even though this function took no ctx parameter and awaits in between.
  const before = store.getStore()?.id
  await new Promise<number>((res) => { setTimeout(() => res(1), 5) })
  const after = store.getStore()?.id
  console.log(`req ${before}: id survived the await -> ${after}`)
}

async function handle(id: number): Promise<void> {
  await store.run({ id }, async () => {
    await loadUser()
  })
}

async function main(): Promise<void> {
  await handle(1)
  await handle(2)

  // A setTimeout scheduled inside run(...) also carries the context to its fire.
  store.run({ id: 99 }, () => {
    setTimeout(() => {
      console.log(`deferred callback still sees req ${store.getStore()?.id}`)
    }, 5)
  })

  // getStore() is `RequestCtx | undefined` — outside any run it is a real
  // `undefined`, as in Node (TDD-00187).
  console.log(store.getStore() === undefined ? 'no ambient context outside a run' : '?')
}
main()
View source on GitHubexamples/async/async_local_storage.ts