NodeJS中如何在并发执行的Promise间共享数据?
Node.js 并发Promise间共享数据的标准实现方式
在Node.js中处理并发Promise间的共享状态(比如你这里的已选好友列表),核心是保证状态修改的原子性——因为Node.js是单线程异步模型,虽然没有多线程的竞态问题,但异步任务会在事件循环中交替执行,直接修改共享状态可能导致冲突。常用的标准方式有以下几种:
1. 集中式状态控制(最推荐,逻辑清晰)
把好友选取的逻辑抽离成一个独立的可复用工具类,所有并发任务都通过这个统一入口获取未被选中的好友,确保选取操作的唯一性。
比如实现一个FriendPicker类:
class FriendPicker { private availableFriends: Person[]; constructor(friends: Person[]) { // 复制原数组,避免修改原始数据 this.availableFriends = [...friends]; } // 原子性地随机选取n个好友 pickRandom(n: number): Person[] { const picked: Person[] = []; // 每次选取后从可用列表中移除,确保不会重复 while (picked.length < n && this.availableFriends.length > 0) { const randomIndex = Math.floor(Math.random() * this.availableFriends.length); picked.push(this.availableFriends.splice(randomIndex, 1)[0]); } return picked; } }
再修改业务代码,让两个并发方法共享同一个FriendPicker实例:
async generateSuggestedSparks(userId: GraphID, friends: Person[], latLng: LatLng) { const friendPicker = new FriendPicker(friends); let suggestions = await this.generateAndMergeListsInParallel([ // 把picker实例传给两个方法 this.generateHardCodedSparks(userId, friendPicker, latLng), this.generateRandomActivitySparks(userId, friendPicker, latLng, 6), ]); suggestions = this.filterDuplicateSuggestions(suggestions); return suggestions.slice(0, 7); } // 修改两个方法,接收FriendPicker而非原始好友列表 async generateHardCodedSparks(userId: GraphID, picker: FriendPicker, latLng: LatLng) { // 根据需求选取需要的好友数量 const pickedFriends = picker.pickRandom(3); // 生成对应的sparks逻辑... return pickedFriends.map(friend => ({/* spark数据结构 */})); } async generateRandomActivitySparks(userId: GraphID, picker: FriendPicker, latLng: LatLng, count: number) { const pickedFriends = picker.pickRandom(count); // 生成随机活动sparks逻辑... return pickedFriends.map(friend => ({/* spark数据结构 */})); }
这种方式逻辑集中,所有状态修改都通过同一个入口,天然避免竞态问题,代码可读性和可维护性都很高。
2. 互斥锁(Mutex)实现原子操作
如果不想抽离状态管理逻辑,可以用Promise实现一个简单的互斥锁,确保同一时间只有一个异步任务可以修改共享状态。
比如实现一个基础Mutex:
class Mutex { private lock: Promise<void> | null = null; private resolveLock: (() => void) | null = null; async acquire(): Promise<void> { // 如果锁已被占用,等待锁释放 if (this.lock) { await this.lock; } // 创建新的锁Promise this.lock = new Promise(resolve => { this.resolveLock = resolve; }); } release(): void { if (this.resolveLock) { this.resolveLock(); this.lock = null; this.resolveLock = null; } } }
然后在业务代码中使用:
async generateSuggestedSparks(userId: GraphID, friends: Person[], latLng: LatLng) { // 复制好友列表作为共享状态 const availableFriends = [...friends]; const mutex = new Mutex(); // 包装任务,确保操作共享状态前加锁 const hardCodedTask = async () => { await mutex.acquire(); try { // 随机选取并移除好友的逻辑 const picked: Person[] = []; const pickCount = 3; while (picked.length < pickCount && availableFriends.length > 0) { const idx = Math.floor(Math.random() * availableFriends.length); picked.push(availableFriends.splice(idx, 1)[0]); } return this.generateHardCodedSparks(userId, picked, latLng); } finally { mutex.release(); } }; const randomTask = async () => { await mutex.acquire(); try { const picked: Person[] = []; while (picked.length < 6 && availableFriends.length > 0) { const idx = Math.floor(Math.random() * availableFriends.length); picked.push(availableFriends.splice(idx, 1)[0]); } return this.generateRandomActivitySparks(userId, picked, latLng, 6); } finally { mutex.release(); } }; let suggestions = await this.generateAndMergeListsInParallel([hardCodedTask(), randomTask()]); suggestions = this.filterDuplicateSuggestions(suggestions); return suggestions.slice(0, 7); }
这种方式适合无法集中状态管理的场景,但需要手动管理锁的获取和释放,代码相对繁琐。
3. SharedArrayBuffer与Atomics(适合多线程场景)
如果你的业务涉及Worker线程(多线程),可以使用SharedArrayBuffer共享内存,并通过Atomics API保证原子操作。但对于单线程下的异步并发,前两种方式足够,这种方式属于过度设计,一般不推荐。
总结
对于你当前的场景,**集中式状态控制(FriendPicker类)**是最标准、最易维护的实现方式,它把状态修改逻辑封装起来,从根源上避免了并发冲突。
内容的提问来源于stack exchange,提问作者ashissl
相关产品推荐
相关产品推荐

