I want to use Node to fire up two child processes in a particular order at a particular time, console logging their stdout as they stream, occasionally switching between the two.
`Proc 1 log # 1`
`Proc 1 log # 2`
`Proc 1 log # 3`
`Proc 1 log # 4`
`Proc 2 log # 1`
`Proc 2 log # 2`
`Proc 2 log # 3`
`Proc 2 log # 4`
`Proc 1 log # 9`
`Proc 1 log # 10`
`Proc 1 log # 11`
`Proc 1 log # 12`
`Proc 1 log # 13`
`Proc 1 log # 14`
`Proc 1 log # 15`
`All procs have finished!`
Of course it's super easy to do. Imperatively. But it's also really ugly & stateful and just urgh. So I'm trying to do it purely, and have built up the computation using the Task monad from folktale (the old one, that is), threading through a stateful object by hand like a chump:
// main _ :: Task error {childProcs}
const main = startProc1({})
.chain(logUntilProc1IsReady)
.chain(startProc2)
.chain(logUntilProc2IsReady)
.chain(logUntilProc1IsFinished)
Much prettier. It would be much better too, if it worked!
`Proc 1 log # 1`
`Proc 1 log # 2`
`Proc 1 log # 3`
`Proc 1 log # 4`
`Proc 2 log # 1`
`Proc 1 log # 6` // <-- These should not be logged
`Proc 2 log # 2`
`Proc 1 log # 7`
`Proc 2 log # 3`
`Proc 1 log # 8`
`Proc 2 log # 4`
`Proc 1 log # 9`
`Proc 1 log # 10` // <-- now it's logging twice! :confounded:
`Proc 1 log # 10`
`Proc 2 log # 6`
`Proc 1 log # 11`
`Proc 1 log # 11`
`Proc 2 log # 7`
`Proc 1 log # 12`
`Proc 1 log # 12`
`Proc 2 log # 8`
`Proc 1 log # 13`
`Proc 1 log # 13`
`Proc 2 log # 9`
`Proc 1 log # 14`
`Proc 1 log # 14`
`Proc 2 log # 10`
`All procs have finished!`
Here is the logging function:
// logStreamUntil :: int -> (a -> bool) -> proc -> string -> Task error State () {childProcs}
const logStreamUntil = curry((predFunc, procName, procObj) =>
new Task ((_, res) => {
const proc = procObj[procName]
const logUntilPred = data =>
predFunc(data)
? (rmAllListeners(proc), res(procObj))
: console.log(data)
proc.stdout.on('data', logUntilPred)
}))
Of which the tl;dr: is that I'm sending it a process name and the object to pluck the actual child-process from, as well as a predicate function used to decide for how long to console log the stdout of whichever child-process had been thrown at it. The predicates just look for something specific in the string from stdout. So it console logs the output when the predicate function returns false, else it stops logging, removes the listener and that should be that!
And here then is the rmAllListeners function:
// rmAllListeners :: childProc -> childProc
const rmAllListeners = proc => (proc.removeAllListeners(), proc.stdout.unref())
Which latter is clearly the issue. Listeners, despite being namespaced and supposedly obliterated by the above, are not being. And I don't know why?!? Help!
There's a repo of it too for those interested in seeing the entire thing: you can find it here.
You're removing listeners from the proc instead of from stdout. The doubles are appearing because you're attaching a second copy of your listener to the 'data' events on the proc.stdout.
Adding .stdout in rmAllListeners fixes it for me:
diff --git a/why-log-twice.js b/why-log-twice.js
index 276d15c..6c15467 100644
--- a/why-log-twice.js
+++ b/why-log-twice.js
@@ -7,7 +7,7 @@ const PROC_ONE_PATH = `node child-proc "Proc 1 log # "`
const PROC_TWO_PATH = `node child-proc "Proc 2 log # "`
// rmAllListeners :: childProc -> childProc
-const rmAllListeners = proc => (proc.removeAllListeners(), proc.stdout.unref())
+const rmAllListeners = proc => (proc.stdout.removeAllListeners(), proc.stdout.unref())
// procIsReady :: string -> bool
const procIsReady = str => str.includes('5')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With