You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何子线程修改Async Local Storage会导致父线程存储变更?

Async Local Storage 理解误区与预期效果实现

我在Node.js HTTP服务器中使用Async Local Storage,为每个请求提供唯一的存储。原本以为执行异步函数时,异步函数内部的存储和父线程一致,但子线程的修改不会同步到父线程。但运行以下代码后,结果和预期不符:

const als = new AsyncLocalStorage<any>();
const wait = (seconds: number) =>
    new Promise(res => setTimeout(res, seconds * 1000));

als.run(new Map(), async () => {
    als.getStore().set('key', 1);
    console.log('outside', als.getStore().get('key'));
    foo();
    bar();
    await wait(2);
    console.log('outside', als.getStore().get('key'));
});

async function foo(): Promise<void> {
    return new Promise(async (resolve, _) => {
        console.log('inside foo', als.getStore().get('key'));
        await wait(1);
        als.getStore().set('key', 2);
        console.log('inside foo', als.getStore().get('key'));
        resolve();
    });
}

async function bar(): Promise<void> {
    return new Promise(async (resolve, _) => {
        console.log('inside bar', als.getStore().get('key'));
        await wait(1);
        als.getStore().set('key', 3);
        console.log('inside bar', als.getStore().get('key'));
        resolve();
    });
}

控制台输出:

outside 1
inside foo 1
inside bar 1
inside foo 2
inside bar 3
outside 3

我原本预期最后一行是outside 1,是不是对Async Local Storage的理解有误?如果是,能否实现我预期的效果?


你的理解确实有偏差,Async Local Storage的核心是共享同一个存储实例,而不是为每个异步分支复制一份存储:

  • 所有在als.run()回调及后续异步操作中获取的getStore(),都是同一个Map对象的引用。所以不管在哪个异步函数里修改这个Map,都会影响到所有持有该引用的地方,包括父级的回调函数。
  • 你预期的“子线程修改不同步到父线程”,本质是需要每个异步分支拥有独立的存储副本,而不是共享同一个实例。

要实现预期效果,需要在每个异步函数内部手动创建存储副本并重新运行上下文:

const als = new AsyncLocalStorage<any>();
const wait = (seconds: number) =>
    new Promise(res => setTimeout(res, seconds * 1000));

als.run(new Map(), async () => {
    als.getStore().set('key', 1);
    console.log('outside', als.getStore().get('key'));
    foo();
    bar();
    await wait(2);
    console.log('outside', als.getStore().get('key'));
});

async function foo(): Promise<void> {
    // 复制当前存储的内容,创建新的Map
    const currentStore = als.getStore();
    const newStore = new Map(currentStore);
    // 在新的存储上下文里执行逻辑
    return als.run(newStore, async () => {
        console.log('inside foo', als.getStore().get('key'));
        await wait(1);
        als.getStore().set('key', 2);
        console.log('inside foo', als.getStore().get('key'));
    });
}

async function bar(): Promise<void> {
    const currentStore = als.getStore();
    const newStore = new Map(currentStore);
    return als.run(newStore, async () => {
        console.log('inside bar', als.getStore().get('key'));
        await wait(1);
        als.getStore().set('key', 3);
        console.log('inside bar', als.getStore().get('key'));
    });
}

修改后的输出会符合你的预期:

outside 1
inside foo 1
inside bar 1
inside foo 2
inside bar 3
outside 1

核心思路是:每个异步分支需要独立上下文时,不要直接修改共享的存储实例,而是先复制当前存储的状态,再通过als.run()启动新的上下文,这样分支内的修改只会影响自己的副本,不会回溯到父级的存储。

内容的提问来源于stack exchange,提问作者BlueSialia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 09:45:23