回调函数多次触发问题排查:Node.js子进程监听逻辑异常
问题原因与解决方案
我一眼就发现了问题所在:你的data事件回调可能被多次触发,因为Node.js会将子进程的标准输出拆分成多个数据块(chunk)发送,即使子进程只输出了一次日志。
当第一个数据块到达时,可能还没包含完整的匹配字符串,所以不会触发回调;当后续数据块到达并拼接后,字符串满足匹配条件,此时你调用了n.stdout.removeAllListeners(),但如果已经有多个data事件的回调任务被放入事件队列,那么已经排队的回调仍然会执行——而此时stdout已经包含了匹配的字符串,所以每次排队的回调都会进入if分支,反复执行你的cb和console.log(33333)。
举个实际场景:子进程输出的完整日志是live-mutex broker is listening on port 8019.,Node.js可能把它拆成两个数据块:
- 第一个块:
live-mutex broker is listen(不匹配正则) - 第二个块:
ing on port 8019.(拼接后满足匹配条件,执行回调) - 如果还有第三个块(比如单独的换行符
\n),此时stdout已经包含匹配字符串,即使你已经移除了监听,但如果这个回调已经在队列里,还是会触发。
修复方案
添加一个执行标志,确保回调只会被调用一次:
export const launchBrokerInChildProcess = function (opts: any, cb: Function) { const host = opts.host || 'localhost'; const port = opts.port || 8019; const detached = Boolean(opts.detached); ping.probe(host, port, function (err, available) { if (err) { return cb(err) } if (available) { log.info(`live-mutex broker/server was already live at ${host}:${port}.`); return cb(null, {host, port, alreadyRunning: true}); } log.info(`live-mutex is launching new broker at '${host}:${port}'.`); const n = cp.spawn('node', [p], { detached, env: Object.assign({}, process.env, { LIVE_MUTEX_PORT: port }) }); if (detached) { n.unref(); } process.once('exit', function () { if (!detached) { n.kill('SIGINT'); } }); n.stderr.setEncoding('utf8'); n.stdout.setEncoding('utf8'); n.stderr.pipe(process.stderr); let stdout = ''; let callbackInvoked = false; // 新增:标记回调是否已执行 n.stdout.on('data', function (d) { if (callbackInvoked) return; // 已执行过回调,直接返回 stdout += String(d); if (stdout.match(/live-mutex broker is listening/i)) { callbackInvoked = true; // 设置标记,防止重复执行 n.stdout.removeAllListeners(); // 下面的n.unref()是冗余代码,之前已经调用过了,可以删除 // if (detached) { n.unref(); } console.log(33333); cb(null, { liveMutexProcess: n, host, port, detached }); } }); }); };
这个标志会在第一次触发回调时被设置为true,后续所有data事件的回调都会直接跳过,彻底避免重复执行。另外,你代码里在匹配到字符串后再次调用n.unref()是多余的,因为在spawn之后已经针对detached情况调用过一次了,可以删掉这部分冗余代码。
内容的提问来源于stack exchange,提问作者Alexander Mills
相关产品推荐
相关产品推荐

