为何子线程修改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
相关产品推荐
相关产品推荐

