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

如何在Node.js中实时读取spawn子进程的输出数据

解决Node.js中child_process实时读取nfc-poll输出的问题

这个问题我之前折腾过好一会儿!核心原因是nfc-poll这个工具在输出到管道(比如Node的child_process创建的子进程stdout)时,会启用块缓冲模式——它不会实时输出每一行内容,而是攒够一块数据或者进程退出时才一次性把缓冲区的内容吐出来,这就是为什么你只有拔卡(进程结束)时才收到data事件。

下面给你两个可行的解决思路:

方案1:强制nfc-poll使用行缓冲输出

我们可以借助stdbuf命令(大部分Linux/macOS系统都自带),强制nfc-poll的stdout使用行缓冲模式,这样每生成一行输出就会立刻发送给Node进程。

修改你的start_process方法:

const cp = require('child_process');
const events = require('events');

function NFCReader() { 
  this.reader = new events.EventEmitter();
  this.start_process(); 
} 

NFCReader.prototype = {
  start_process: function () {
    // 用stdbuf强制行缓冲输出nfc-poll的内容
    this._process = cp.spawn('stdbuf', ['-oL', 'nfc-poll'], {});
    
    this._process.on('close', this.restart_process.bind(this));
    this._process.stdout.on('data', this.handle_data.bind(this));
    this._process.stderr.on('data', this.handle_error.bind(this));
  },
  handle_data: function (data) {
    var _data = data.toString();
    var uid_lines = _data 
      .split('\n')
      .filter(function (line) {return line.search('UID') >= 0});
    
    if (uid_lines.length !== 0) {
      // 可能一次data事件包含多行,遍历处理
      uid_lines.forEach(line => {
        var card_id = line
          .trim()
          .split(':')[1].trim()
          .replace(/[ ]+/g, ':');
        this.reader.emit('card', card_id);
      });
    }
  },
  // 记得补充restart_process和handle_error方法
  restart_process: function() {
    console.log('进程重启');
    this.start_process();
  },
  handle_error: function(err) {
    this.reader.emit('error', err.toString());
  }
};

这里stdbuf -oL的作用是:-o指定stdout,L代表行缓冲模式。这样nfc-poll每输出一行,就会立刻把内容推送给Node,你的handle_data就能实时触发了。

方案2:用readline模块逐行解析流

如果你的环境没有stdbuf,可以用Node自带的readline模块来逐行读取stdout流,它会帮你处理缓冲,实时捕获每一行输出。

修改后的代码示例:

const cp = require('child_process');
const events = require('events');
const readline = require('readline');

function NFCReader() { 
  this.reader = new events.EventEmitter();
  this.start_process(); 
} 

NFCReader.prototype = {
  start_process: function () {
    this._process = cp.spawn('nfc-poll', [], {});
    
    // 创建readline接口,绑定到子进程的stdout
    const rl = readline.createInterface({
      input: this._process.stdout,
      crlfDelay: Infinity
    });

    // 监听每一行输出
    rl.on('line', (line) => {
      if (line.includes('UID')) {
        var card_id = line
          .trim()
          .split(':')[1].trim()
          .replace(/[ ]+/g, ':');
        this.reader.emit('card', card_id);
      }
    });
    
    this._process.on('close', () => {
      rl.close();
      this.restart_process.bind(this)();
    });
    this._process.stderr.on('data', this.handle_error.bind(this));
  },
  // 补充其他方法...
  restart_process: function() {
    console.log('进程重启');
    this.start_process();
  },
  handle_error: function(err) {
    this.reader.emit('error', err.toString());
  }
};

readline会自动处理流的缓冲问题,只要子进程输出一行,line事件就会触发,这样你就能在放卡后立刻拿到UID了。

额外提示

你原来代码里的this._process.stdout.readableFlowing = true其实是多余的——当你绑定data事件到stdout流时,Node会自动把流切换到流动模式,不需要手动设置。

内容的提问来源于stack exchange,提问作者Loïc Faure-Lacroix

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:58:47