Search code examples
javascriptphpnode.jses6-promise

Call Node script with PHP exec and return data to PHP before finally method


I have a PHP script which uses the exec function to execute a Node script and return some data back to the same PHP script.

My issue is that I need to return the data back to PHP without having to wait for the cleanup code in finally to finish.

I have written some example code below to show you how the code flows and illustrate my issue. The code example does not use any node modules but feel free to use them if they will help.

example.php

$data = 'hello world';

$exec = 'node example.js ' . $data;
$escaped_command = escapeshellcmd($exec);
$data = exec($escaped_command);

// waits 10 seconds then displays returned data
// I need the code to return before finally executes
var_dump($data); // string(31) "Final data is final hello world"

example.js

   let data = process.argv[2];
// let data = "hello world";

async function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

const doSomething = data => new Promise((resolve, reject) => {

    if (data) {
        resolve("new hello world");
    } else {
        reject();
    }
});

const doSomethingElse = newData => new Promise((resolve, reject) => {

    if (newData) {
        resolve("final hello world");
    } else {
        reject();
    }
});


(async() => {

    try {

        let newData = await doSomething(data);

        let finalData = await doSomethingElse(newData);

        // return this to server/php right now. 
        // Do not wait until finally script finishes
        console.log("Final data is " + finalData);

    } catch (err) {

        // if error return this to server/php right now. 
        // Do not wait until finally script finishes
        console.log(err);

    } finally {

        // does cleanup/closes node app
        await timeout(10000);
    }

})();

Solution

  • Use proc_open to run a process asynchronously. Let node output data as soon as possible. This data can be read without waiting for node to finish:

    // foo.js
    process.stdout.write(JSON.stringify({"foo":"bar"})+"\n");
    
    // simulate cleanup
    setTimeout(() => {}, 3000);  
    
    
    // foo.php
    <?php
    
    $io = [
        0 => ['pipe', 'r'], // node's stdin
        1 => ['pipe', 'w'], // node's stdout
        2 => ['pipe', 'w'], // node's stderr
    ];
    
    $proc = proc_open('node foo.js', $io, $pipes);
    
    $nodeStdout = $pipes[1]; // our end of node's stdout
    echo date('H:i:s '), fgets($nodeStdout);
    
    proc_close($proc);
    echo date('H:i:s '), "done\n";
    

    Sample output:

    $ php foo.php 
    14:59:03 {"foo":"bar"}
    14:59:06 done
    

    proc_close will wait for the node process to exit, but you can defer that waiting until you're done with other things in PHP.

    If you don't call proc_close, you may end up with zombie processes, depending on your environment. Docker and the PID 1 zombie reaping problem contains a good explanation. It refers to docker a lot, because this is where zombies frequently occur, but it is still generally applicable.

    Some Server APIs have provisions for running code "invisibly" for the client. For instance, fcgi has fastcgi_finish_request, which you can call before proc_close. Then HTTP clients wouldn't notice the waiting time.

    If you can't tolerate eventually waiting for node, consider making the node thing a webservice instead, which is managed independently of PHP.