@esinx/async-context

中文文档
1.0.0-2发布于 2025/09/09MITTypeScriptESM + CJS零依赖近 30 天下载 2928

A TypeScript library for async context management

原始使用文档
文档目录24 节
  1. Installation
  2. What is Async Context?
  3. The Problem
  4. The Solution
  5. Installation
  6. Quick Start
  7. API Reference
  8. createContext<T>(initialValue: T): ContextHandle<T>
  9. ContextHandle<T>
  10. use<T>(handle: ContextHandle<T>): T
  11. runWithContext<T>(handle: ContextHandle<T>, value: T)
  12. Common Patterns
  13. Request Tracing
  14. User Context
  15. Database Transactions
  16. Nested Contexts
  17. Advanced Usage
  18. Context Composition
  19. Context Providers (React-like pattern)
  20. Best Practices
  21. Error Handling
  22. Performance Considerations
  23. Requirements
  24. License

@esinx/async-context

A TypeScript library for async context management, using React-like context API powered by Node.js AsyncLocalStorage.

Installation

npm install @esinx/async-context
# or
yarn add @esinx/async-context
# or
pnpm add @esinx/async-context

What is Async Context?

Async context allows you to maintain contextual information throughout an async call chain without explicitly passing it through every function parameter. This is particularly useful for:

  • Request tracing: Maintain request IDs across async operations
  • User context: Keep user information available throughout request handling
  • Logging: Automatically include contextual information in logs
  • Database transactions: Maintain transaction context across async operations
  • Feature flags: Access feature flag state throughout your application

The Problem

In traditional async JavaScript/TypeScript code, passing context through multiple layers can be cumbersome:

// Without async context - tedious parameter passing
async function handleRequest(userId: string, requestId: string) {
    const user = await fetchUser(userId, requestId)
    const data = await processData(user, requestId)
    await saveResult(data, userId, requestId)
}

async function fetchUser(userId: string, requestId: string) {
    console.log(`[${requestId}] Fetching user ${userId}`)
    // ... fetch logic
}

async function processData(user: User, requestId: string) {
    console.log(`[${requestId}] Processing data for ${user.id}`)
    // ... processing logic
}

The Solution

With async context, you can maintain this information implicitly:

// With async context - clean and maintainable
const userContext = createContext<User | null>(null)
const requestContext = createContext<string>('')

async function handleRequest(userId: string, requestId: string) {
    await requestContext.run(requestId, async () => {
        const user = await fetchUser(userId)
        await userContext.run(user, async () => {
            const data = await processData()
            await saveResult(data)
        })
    })
}

async function fetchUser(userId: string) {
    const requestId = use(requestContext)
    console.log(`[${requestId}] Fetching user ${userId}`)
    // ... fetch logic
}

async function processData() {
    const user = use(userContext)
    const requestId = use(requestContext)
    console.log(`[${requestId}] Processing data for ${user.id}`)
    // ... processing logic
}

Installation

npm install async-context
# or
yarn add async-context
# or
pnpm add async-context

Quick Start

import { createContext, use } from 'async-context'

// Create a context with a default value
const userContext = createContext<string>('anonymous')

// Run code with a specific context value
await userContext.run('john-doe', async () => {
    // All async operations in this scope have access to the context
    const currentUser = use(userContext) // 'john-doe'

    await someAsyncOperation()

    // Still available after async operations
    const stillCurrentUser = use(userContext) // 'john-doe'
})

// Outside the run scope, back to default
const defaultUser = use(userContext) // 'anonymous'

API Reference

createContext<T>(initialValue: T): ContextHandle<T>

Creates a new context handle with an initial/default value.

const stringContext = createContext<string>('default')
const numberContext = createContext<number>(0)
const userContext = createContext<User | null>(null)

ContextHandle<T>

The object returned by createContext with the following methods:

handle.run<Result>(value: T, fn: () => Promise<Result>): Promise<Result>

Runs an async function with a specific context value.

const result = await handle.run('new-value', async () => {
    // Context is 'new-value' within this scope
    return await someAsyncOperation()
})

handle.use(): T

Gets the current context value (same as the standalone use function).

const currentValue = handle.use()

use<T>(handle: ContextHandle<T>): T

Gets the current value from a context handle.

const currentUser = use(userContext)

runWithContext<T>(handle: ContextHandle<T>, value: T)

Creates a reusable function that runs operations with a specific context value.

const runAsAdmin = runWithContext(userContext, adminUser)

const result = await runAsAdmin(async () => {
    // This runs with adminUser as the context
    return await performAdminOperation()
})

Common Patterns

Request Tracing

import { createContext, use } from 'async-context'

const requestIdContext = createContext<string>('')

// Express middleware
app.use((req, res, next) => {
    const requestId = req.headers['x-request-id'] || generateId()
    requestIdContext.run(requestId, () => {
        next()
    })
})

// In any handler or service
async function processOrder() {
    const requestId = use(requestIdContext)
    logger.info(`Processing order`, { requestId })
    // ... processing logic
}

User Context

interface User {
    id: string
    role: string
    permissions: string[]
}

const userContext = createContext<User | null>(null)

// Authentication middleware
async function authenticate(token: string) {
    const user = await verifyToken(token)
    return userContext.run(user, async () => {
        // All subsequent operations have access to user context
        return await handleRequest()
    })
}

// In any service
async function createPost(data: PostData) {
    const currentUser = use(userContext)
    if (!currentUser) {
        throw new Error('Not authenticated')
    }

    if (!currentUser.permissions.includes('create:post')) {
        throw new Error('Insufficient permissions')
    }

    // ... create post logic
}

Database Transactions

const transactionContext = createContext<Transaction | null>(null)

async function withTransaction<T>(fn: () => Promise<T>): Promise<T> {
    const transaction = await db.beginTransaction()

    try {
        const result = await transactionContext.run(transaction, fn)
        await transaction.commit()
        return result
    } catch (error) {
        await transaction.rollback()
        throw error
    }
}

// Usage
await withTransaction(async () => {
    await createUser(userData)
    await createProfile(profileData)
    // Both operations use the same transaction automatically
})

Nested Contexts

const tenantContext = createContext<string>('default')
const userContext = createContext<User | null>(null)

await tenantContext.run('tenant-123', async () => {
    await userContext.run(user, async () => {
        // Both contexts are available
        const tenant = use(tenantContext) // 'tenant-123'
        const currentUser = use(userContext) // user object

        await performOperation()
    })
})

Advanced Usage

Context Composition

interface AppContext {
    user: User | null
    requestId: string
    tenant: string
}

const appContext = createContext<AppContext>({
    user: null,
    requestId: '',
    tenant: 'default',
})

// Helper function to create context
function createAppContext(user: User, requestId: string, tenant: string) {
    return { user, requestId, tenant }
}

await appContext.run(createAppContext(user, requestId, tenant), async () => {
    const { user, requestId, tenant } = use(appContext)
    // ... application logic
})

Context Providers (React-like pattern)

class ContextProvider {
    constructor(
        private userContext = createContext<User | null>(null),
        private requestContext = createContext<string>('')
    ) {}

    async provideContext<T>(
        user: User,
        requestId: string,
        fn: () => Promise<T>
    ): Promise<T> {
        return this.requestContext.run(requestId, async () => {
            return this.userContext.run(user, fn)
        })
    }

    getCurrentUser() {
        return use(this.userContext)
    }

    getCurrentRequestId() {
        return use(this.requestContext)
    }
}

Best Practices

  1. Use meaningful default values: Choose defaults that make sense for your application
  2. Keep context immutable: Don't modify context objects; create new ones instead
  3. Avoid context for frequently changing data: Context is best for relatively stable information
  4. Use TypeScript: Take advantage of type safety for your context values
  5. Document your contexts: Make it clear what each context represents and when it's available
  6. Test with context: Include context scenarios in your tests

Error Handling

Context is automatically cleaned up when errors occur:

try {
    await userContext.run(user, async () => {
        // Context is available here
        throw new Error('Something went wrong')
    })
} catch (error) {
    // Context is back to default/initial value
    const currentUser = use(userContext) // back to initial value
}

Performance Considerations

  • AsyncLocalStorage has minimal overhead for most applications
  • Context lookup is very fast (O(1))
  • Nested contexts have minimal additional cost
  • Consider using context composition for complex state rather than many individual contexts

Requirements

  • Node.js 16+ (AsyncLocalStorage support)
  • TypeScript 4.5+ (for best type support)

License

MIT

版本与发布记录

@esinx/async-context 共发布 3 个版本,首次发布于 2025/06/28;过去 12 个月发布了 0 个版本。

版本发布日期距今
1.0.0-2latest2025/09/091.2 年前
1.0.0-12025/06/281.4 年前
1.0.02025/06/281.4 年前

关于 @esinx/async-context 的常见问题

@esinx/async-context 是什么?

A TypeScript library for async context management

如何安装 @esinx/async-context?

在项目目录运行 npm install @esinx/async-context,或使用 pnpm add / yarn add / bun add。

@esinx/async-context 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@esinx/async-context 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@esinx/async-context 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=16。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@esinx/async-context 可以商用吗?

@esinx/async-context 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@esinx/async-context 的下载量有多少?

根据 npm 官方下载统计,@esinx/async-context 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,928 次,日均约 98 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@esinx/async-context 还在维护吗?

最新版本 1.0.0-2 发布于 2025/09/09(1.2 年前);至今共发布 3 个版本,其中过去 12 个月发布了 0 个;首次发布于 2025/06/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。