Node.js事件处理机制及多次触发登录事件的文件写入异常排查
问题原因分析
你遇到的不稳定结果是异步竞态条件导致的,具体原因如下:
- EventEmitter的触发机制:
login.emit()是同步执行的,连续调用四次emit时,四个login事件的监听器会被同时触发,各自独立启动异步操作,互相之间不会等待对方完成。 - 文件读写的竞态:四个监听器几乎同时执行
fs.promises.readFile,此时userlist.txt里是空数组,所有监听器都读到空数组。随后每个监听器往数组里push自己的用户名,再执行writeFile。因为写操作是异步的,最终谁最后完成写操作,就会把自己的数组覆盖到文件里,导致最终结果只保留最后一个完成的用户名(或随机某一个,取决于异步任务的完成顺序)。
Node.js EventEmitter核心特性
- 同步触发监听器:调用
emit()时,会立即同步调用所有注册的监听器函数。如果监听器是异步函数(带async),emit()不会等待该异步函数完成,直接继续执行后续代码。 - 无内置异步串行处理:EventEmitter本身不处理异步监听器的执行顺序,多个异步监听器会并行执行,涉及共享资源(比如文件)操作时,极易引发竞态问题。
解决方案
方案1:串行执行异步任务(推荐)
将用户添加逻辑封装为异步函数,通过await确保每次操作完成后再执行下一次,彻底避免竞态:
const fs = require('fs'); async function addUser(username) { try { const userList = await fs.promises.readFile("./userlist.txt", 'utf-8'); const parsedUserList = JSON.parse(userList); parsedUserList.push(username); await fs.promises.writeFile("./userlist.txt", JSON.stringify(parsedUserList)); console.log(`user logged in successfully... ${username}`); } catch (error) { console.log(error); } } // 串行执行所有添加操作 async function run() { await addUser("Jhon"); await addUser("Smith"); await addUser("Jos"); await addUser("Test"); } run();
方案2:基于EventEmitter实现串行队列
如果必须保留EventEmitter的使用方式,可以维护一个异步任务队列,确保每个事件的处理等待前一个完成:
const fs = require('fs'); const EventEmitter = require('events'); const login = new EventEmitter(); // 初始化空Promise作为队列起点 let taskQueue = Promise.resolve(); login.on("login", (username) => { // 将当前任务加入队列,等待前一个任务完成后执行 taskQueue = taskQueue.then(async () => { try { const userList = await fs.promises.readFile("./userlist.txt", 'utf-8'); const parsedUserList = JSON.parse(userList); parsedUserList.push(username); await fs.promises.writeFile("./userlist.txt", JSON.stringify(parsedUserList)); console.log(`user logged in successfully... ${username}`); } catch (error) { console.log(error); } }); }); login.emit("login", "Jhon"); login.emit("login", "Smith"); login.emit("login", "Jos"); login.emit("login", "Test");
方案3:文件锁(进阶)
针对多进程共享文件的复杂场景,可以使用fs.promises.flock实现文件锁,确保同一时间只有一个进程能读写文件,不过这种方式相对复杂,适合特定场景。
内容的提问来源于stack exchange,提问作者Alok Shaw
相关产品推荐
相关产品推荐

