pythonnode.jsipcchild-processspawn

NodeJS child_process spawn not returning live stdout


I'm trying to run something along the lines of this python script from a javascript file (in an electron project), and echo the output in real-time:

import time

def main():
    print("Hello 1")
    time.sleep(1)
    print("Hello 2")
    time.sleep(1)
    print("Hello 3")

    return 5

if __name__ == "__main__":
    result = main()
    print(f"result: {result}")

And I'm using the following Javascript code to execute the file:

// ...

const child = spawn(runners[lang], commandArgs, {shell: true});
child.stdout.setEncoding('utf8');

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

What I'm seeing though is all the output being printed out in one giant blob after the whole script finishes running, and not "Hello 1", etc. being sent to my javascript line-by-line. Basically, it seems that my child.stdout.on callback is only running once. Is there a specific reason for this, and how can I receive data in child.stdout.on as soon as print is called in the python script?


Solution

  • To get the expected output, you need to forcibly flush the stdout stream in your python program. This can be done by adding flush=True argument.

    import time
    
    def main():
        print("Hello 1", flush=True)
        time.sleep(1)
        print("Hello 2", flush=True)
        time.sleep(1)
        print("Hello 3", flush=True)
    
        return 5
    
    if __name__ == "__main__":
        result = main()
        print(f"result: {result}", flush=True)
    

    Alternatively you could launch the python interpreter with -u option. This will force the stdout and stderr streams to be unbuffered. By doing so, you won't have to specify flush=True on every print function call.

    const { spawn } = require('node:child_process');
    
    const child = spawn("python", ["-u", "your_file_name.py"], {shell: true});
    child.stdout.setEncoding('utf8');
    
    child.stdout.on('data', (data) => {
        console.log(`Received data: ${data}`);
    });