Node.js script to run multiple commands in parallel
If you encountered a case where you wanted to run multiple commands in parallel, the usual suggestion is to use the shell's &
operator to send commands to the background. This approach kind of works, but it has its quirks.
I often ended up with a process hanging in the background, multiple outputs being mangled, or the output of some commands completely missing. Bash scripting wizards can probably solve all of these issues, but I turned to JavaScript, as it is the language I know best.
If you’re eager to see the code, feel free to jump to it.
Why not an existing library?
In my professional projects, I would probably just use concurrently. However, this time I decided to write a poor man's version of it. Although simple and basic, it covers my specific use case.
It was mostly for learning purposes, but sometimes I create things from scratch for fun or to avoid installing a large library for a tiny piece of it.
My requirements
- Run multiple commands in parallel.
- Terminate all of the commands if any of them fail.
- Prefix each command's output with its color-coded (In the finished version, I added an option to disable colors, as I know some people really don't like colors in their terminals) name.
I also decided to keep it simple:
- No CLI version to avoid parsing the arguments.
- Name (prefix) should be a mandatory input.
Usage
To use the script, you need to import it and pass the list of your commands (along with their names) to it. Like this:
;
;
new Runnertasks;
This is how the script looks in action:
Source code
Here is the complete script:
;
Approach
If you are still reading, you are probably interested in what I actually did.
Code in the following steps is simplified to make it easier to follow. Check the source code above for the implementation details.
Spawn a process for each command
As its name suggests, the Node.js native spawn
method can be used to spawn a child process for each of the commands:
// Split the command into the command and its arguments
// For example, "npm run build" becomes:
// - command: "npm"
// - args: ["run", "build"]
;
;
// Spawn the child process
;
We'll store a reference to the child process and its name in order to use it later:
;
Exit if any of the commands fail
Each process has error
and close
events. We'll listen to them, and if they happen terminate all of the other processes:
// When any of the processes exits, terminate the others
// (we'll do the same for the "error" event)
'close',;
Prefix each command's output
The child process's stdout
and stderr
data
events are triggered whenever the process outputs data or errors. This allows us to listen to these events, prefix the output, and forward it to the main script's output.
// Prefix the output and write to stdout (we'll do the same for stderr)
'data',;
Please note that name
is already colored using the method I wrote in the past.
The addPrefix
method is fairly simple. It splits the text into lines and prefixes it with the command's name:
text, prefix
And that is pretty much it,
Conclusion
I hope for two things - that you'll find the script useful, and that this post will inspire you to try hacking on your own before reaching out for an existing library.
My Runner
is less than a hundred lines of code and I plan to continue using it in small projects. Feel free to use and modify it to your liking.
Of course, if you want something more robust, stick with something like concurrently, but use these opportunities to learn something new.