Search code examples
javascriptnode.jstypescriptpromisespawn

returning promises for sequential runing child processes


I am trying to spawn a child process and have its results in a Promise. The child process can not run multiple times at the same time.

I am wrapping node.js's child_process.spawn() in a Promise. The promise fulfills when the process exits successful and rejects if otherwise. Requests com in at different times sometimes one sometimes multiple. I need to execute the same command for every request maybe even multiple times (with different or possibly the same options) to fulfill the requests. But the command will lock up if run in parallel. So it can not be run without making sure it exited beforehand.

Maybe i need them to queue up?

I can't wrap my head around how to do this in JavaScript / Typescript. Busy waiting is obviously no god idea, but i hope it explains what I want to do here.

export class RunThingy{

private busy: boolean;

constructor() {
  this.busy = false;
}

private run(options: string[]): Promise<string> {
  return new Promise((resolve, reject) => {
    let stdout: string = '';
    let stderr: string = '';
    while (!this.busy) {             //Problem
      this.busy = true;
      let process: ChildProcess = spawn('processName', options); 
      process.stdout.on('data', (contents) => { stdout += contents; });
      process.stderr.on('data', (contents) => { stderr += contents; });
      process
        .on('error', reject)
        .on('close', function (code) {
          if (code === 0) {
            resolve(stdout);
          } else {
            reject(stderr);
          }
          this.buisy = false;        //Problem
        });
    }
  });
}

Edit: renamed command[] to options[]


Solution

  • A promise can be rejected or resolved once. Each process must be wrapped in a promise. Here is a proposition:

    export class RunThingy {
        private curCommand: Promise<string> | null
    
        private run(options: string[]): Promise<string> {
            let next: Promise<string>
            if (this.curCommand) {
                next = this.curCommand.then(
                    () => runCommand(options), // the next command will be started after
                    () => runCommand(options)  // the current will be resolved or rejected
                )
            } else
                next = runCommand(options)
            next = next.then(stdout => {
                if (next === this.curCommand) // if this is the last command
                    this.curCommand = null    // then forget the current command
                return stdout                 // return the command value
            }, err => {
                if (next === this.curCommand) // if this is the last command
                    this.curCommand = null    // then forget the current command
                throw err                     // throw again the error
            })
            this.curCommand = next
            return this.curCommand
        }
    }
    
    function runCommand(options: string[]): Promise<string> {
        return new Promise((resolve, reject) => {
            let stdout = '';
            let stderr = '';
            let process: ChildProcess = spawn('processName', options);
            process.stdout.on('data', (contents) => { stdout += contents; });
            process.stderr.on('data', (contents) => { stderr += contents; });
            process
                .on('error', reject)
                .on('close', function (code) {
                    if (code === 0) {
                        resolve(stdout);
                    } else {
                        reject(new Error(stderr));
                    }
                });
        });
    }
    

    In the method run, we check if there is a current command. The new command will be started after the current command will be resolved or rejected.