我目前正在使用 Noble Node.js 模块进行蓝牙 (BLE) 连接。 Noble 模块偶尔会通过 C++ 的
printf
函数将错误直接打印到控制台。 Noble 没有可用于侦听此输出的 Node.js 绑定,这意味着我需要一种方法来手动拦截输出。
我尝试过覆盖 Node 的
console.log
和 process.std...
,但由于 Noble 使用的是 C++ 的 printf
(并直接打印到控制台),因此这种方法不起作用。
我不想直接修改 C++,因为我必须更改
printf
的每个实例,使得这个解决方案将来难以维护。所有拦截都需要在 Node.js 端完成。从子进程通过管道输出控制台输出也不太理想。
有没有简单的方法可以做到这一点?
我想我已经使用
spawn
提出了自己的解决方案。这是 lib.js
,包含我自己的库代码的文件:
// ============================================ IMPORTS ============================================
import events from "node:events";
import { spawn } from "node:child_process";
// ============================================ GLOBALS ============================================
const emitter = new events.EventEmitter();
let childProcess;
let id = 0;
// ======================================== INITIALIZATION ========================================
function initParentProcess() {
// Create a child process of this file using spawn:
childProcess = spawn("node", ["."], {
stdio: ["pipe", "pipe", "pipe", "ipc"],
env: { IS_CHILD: "true" },
});
// Listen for stdout/stderr output from the child process and forward it to the importing file;
// Note that ".slice(0, -1)" removes ending newlines; you may or may not want this:
childProcess.stdout?.on("data", (data) => {
emitter.emit("stdout", data.toString().slice(0, -1));
});
childProcess.stderr?.on("data", (data) => {
emitter.emit("stderr", data.toString().slice(0, -1));
});
// Once the child process is ready, emit the "ready" event:
childProcess.once("message", (state) => {
if (state === "ready") emitter.emit("ready");
});
}
function initChildProcess() {
// Satisfy the ESLint overlords:
if (!process.send) return;
// Handle incoming requests from the parent process:
process.on("message", async ({ id, func, args: args }) => {
// Satisfy the ESLint overlords:
if (!process.send) return;
// Handle each function and their arguments:
switch (func) {
case "example":
process.send({ id, res: await _example(...args) });
break;
// Other functions should be handled here
}
});
// Notify the parent process that the child process is ready:
process.send("ready");
}
if (!process.env.IS_CHILD) {
// If not a child process, this module must have been imported from another file:
initParentProcess();
} else {
// Otherwise, this instance is the child process:
initChildProcess();
}
// ======================================= ABSTRACTION LAYER =======================================
// The handle function will forward work to the child process, where it will be completed.
// Upon completion, the handle function's Promise will result with the result of the work.
function handle(func, ...args) {
// Handle setup:
const handleID = id++;
const listener = (resolve, id, res) => {
// Only handle the message if the ID matches the handle ID:
if (id !== handleID) return;
// Remove the listener and resolve with the result:
process.removeListener("message", listener);
resolve(res);
};
// Return a Promise that resolves once we recieve a response from the child process:
return new Promise((resolve) => {
childProcess.send({ id: handleID, func, args });
childProcess.on("message", ({ id, res }) => listener(resolve, id, res));
});
}
// ================================= CHILD PROCESS IMPLEMENTATIONS =================================
// This is the actual implementation of "example":
async function _example(sampleArg) {
return `Example output; sampleArg=${sampleArg}`;
}
// ================================ PARENT PROCESS IMPLEMENTATIONS ================================
// This is the module export for the importing file to use:
export async function example(sampleArg) {
return await handle("example", sampleArg);
}
// ====================================== SUPPORTING EXPORTS ======================================
// Event emitter exports so the importing file knows when this module is ready:
export function on(event, callback) {
emitter.on(event, callback);
}
export function once(event, callback) {
emitter.once(event, callback);
}
export function close() {
childProcess.kill();
}
这是
main.js
:
import { once, example, close } from "./lib.js";
once("ready", async () => {
console.log("Ready!");
const result1 = await example("test");
const result2 = await example("hello world");
console.log("Result 1:", result1);
console.log("Result 2:", result2);
close();
});
运行
node main.js
将打印:
Ready!
Result 1: Example output; sampleArg=test
Result 2: Example output; sampleArg=hello world
此外,我现在可以收听来自
stdout
的 stderr
和 main.js
,如下所示:
import { on } from "./lib.js";
on("stdout", (data) => {
console.log(data);
});
on("stderr", (data) => {
console.log(data);
});
其工作方式分为以下步骤:
lib.js
;然后创建一个子进程。"ready"
事件,我们使用 main.js
侦听器在 once
中侦听该事件。example
致电 main.js
。example
中导出的 lib.js
函数,该函数又调用 handle
。handle
函数将我们的请求转发给子进程。handle
函数获取此结果,并解决承诺,有效地将其返回到我们导出的example
函数。example
函数中返回,返回到 main.js
。result1
和result2
。然后我们将结果打印到控制台。对于我想要解决的问题来说,这似乎有点复杂,但如果它有效,它就有效。请告诉我是否有任何方法可以简化此操作,而又不会增加使用
main.js
中的模块的难度。