Node js execute python script one after the other

i have a React app and i want to use Node to execute a number of scripts ( can be N number of scripts ) when a button is click, one after the other. I also have some state for each script, so when the script is done i want to set his state to “completed”.

With some google-ing i ended up with :

function runScript(command, args, options, id, callback) {
  const child = childProcess.spawn(command, args, options);

  child.stdout.on('data', (data) => {
    console.log(`${data}`);
  });

  child.stderr.on('data', (data) => {
    console.log(`${data}`);
  });

  // callback function to change the state to "completed"
  child.on('close', () => callback(id, 'completed'));
}

const deployScripts = () => {
    loadedSteps.forEach((script) => {
      if (script.path.length) {
        const [command, args, options] = getParamBasedOnFile(script.path);
        if (command) {
          //set the script to "active" which means is running
          updateStepStatus(script.id, 'active');
          runScript(command, args, options, script.id, updateStepStatus);
        }
      }
    });
  }; 

It works but of course since its asynchronous is running all the scripts at the same time, which is not what i want.

Then i went and tried to make is synchronous

function runScript(command, args, options) {
  const child = childProcess.spawnSync(command, args, options);

  console.log(child.stdout.toString());
  // callback(id, 'completed');  -> doesnt work
  console.log('exit run script function');
}

const deployScripts = () => {
    loadedSteps.forEach((script) => {
      if (script.path.length) {
        const [command, args, options] = getParamBasedOnFile(script.path);
        if (command) {
          updateStepStatus(script.id, 'active');
          runScript(command, args, options, script.id, updateStepStatus);
          updateStepStatus(script.id, 'completed');
        }
      }
    });
  };

But the problem with this is that even tho it runs the script 1 by 1, (i can see the output in the console), both script states are set to “complete” at the same time (when i tested it 1 python script has sleep of 5 seconds and another one has a sleep of 10 seconds, so the first one should be set to complete, then after 5 seconds the second one).

Not sure whats the best way is to do something like this.

Answer

You can promisify runScript and implement series of steps as async function, this way you can use await to wait for each step to complete before running next step.

const childProcess = require("child_process")

/* mocks */

const loadedSteps = [{path:'foo', id:1}, {path:'bar', id:2}, {path:'baz', id:3}]

function getParamBasedOnFile(path) {
    return ['python', ['-c', "import time;time.sleep(1)"], {}]
}

function updateStepStatus(id, status) {
    console.log(id, status)
}

/* code */

function runScript(command, args, options) {
    return new Promise((resolve, reject) => {
        const child = childProcess.spawn(command, args, options);
        child.on('close', () => resolve(child.stdout.toString()))
    })
}

async function deployScripts() {
    for(const script of loadedSteps) {
        if (script.path.length) {
            const [command, args, options] = getParamBasedOnFile(script.path);
            if (command) {
                updateStepStatus(script.id, 'active');
                await runScript(command, args, options, script.id);
                updateStepStatus(script.id, 'completed');
            }
        }
    }
};

/* main */

deployScripts().then(() => console.log('done'))