Ad

How To Turn Child_process.spawn's "Promise" Syntax To "async/await" Syntax

So I have this code and I'm trying to understand the async/await syntax in full depth. The below is the Promise version of the code:

function callToolsPromise(req) {
    return new Promise((resolve, reject) => {
        let pipshell = 'pipenv';
        let args = ['run', 'tools'];
        req.forEach(arg => {
            args.push(arg)
        });
        tool = spawn(pipshell, args);
        tool.on('exit', (code) => {
            if (code !== 0) {
                tool.stderr.on('data', (data) => {
                    reject(data);
                });
            } else {
                tool.stdout.on ('data', (data) => {
                    resolve(JSON.parse(data)):
                });
            }
        });
    })
}

I have some python code I want to execute in tools/__main__.py so that's why I'm calling "pipenv".

Here's my attempt to do write it in async/await way (which actually works):

async function callToolsAsync(req) {
    let pipshell = 'pipenv';
    let args = ['run', 'tools'];
    req.forEach(arg => {
        args.push(arg)
    });
    let tool = spawn(pipshell, args);
    for await (const data of tool.stdout) {
        return data
    }
}

But all I did was copy and paste from someone's example where I have for await... loop.

Therefore I've been trying to rewrite this same code so that I can actually understand it but I've been failing for days now.

Are there any other ways to write this code with async/await way without using the for await... loop?

Also I have no idea how I can access data except for using the .then syntax:

callToolsAsync(['GET','mailuser'])
.then(console.log)

How else would I access "data" from resolve(data)?

Many thanks.

Ad

Answer

There are probably not better ways to write the code using async/await than using the for async (chunk of stream) syntax in Node. Node streams implement an asynchronous iterator specifically to allow doing so.

This article on 2ality has more in-depth explanation and discussion. MDN articles on Symbol.asyncIterator and for-await...of cover asynchronous iteration more generally.

Once using asychronous iteration has been decided it must be used in an async function, which will return a promise.

While using a then clause on the returned promise is a completely normal way of getting the data, you could also await the result of callToolsAsync(req) - provided of course that the call is coded inside an async function so that await is in a valid context.


The following code experiment gets the stdio and stderr output, and the exit code from a child process. It doesn't use Python or parse data.

main.js (type node main.jsto run)

// main.js
async function spawnChild() {
    const { spawn } = require('child_process');
    const child = spawn('node', ["child.js"]);

    let data = "";
    for await (const chunk of child.stdout) {
        console.log('stdout chunk: '+chunk);
        data += chunk;
    }
    let error = "";
    for await (const chunk of child.stderr) {
        console.error('stderr chunk: '+chunk);
        error += chunk;
    }
    const exitCode = await new Promise( (resolve, reject) => {
        child.on('close', resolve);
    });

    if( exitCode) {
        throw new Error( `subprocess error exit ${exitCode}, ${error}`);
    }
    return data;
}

spawnChild().then(
    data=> {console.log("async result:\n" + data);},
    err=>  {console.error("async error:\n" + err);}
);

child.js

// child.js
console.log( "child.js started"); //  stdout
setTimeout( finish, 1000);
function finish() {
    console.log( "child.js: finish() call");  //  stdout 
    console.error("child exit using code 1"); //  stderr
    process.exit(1);
}

This showed

  • A console warning that async iteration of a readable stream is still experimental in node,
  • The for await (chunk of stream) loops seem to loop until the stream is closed - in this case meaning that await will wait on an open stream that doesn't have data available at the time.
  • retrieving stdout and stderr content from their pipes, and getting the exit code can be done in no particular order since retrieval is asynchronous.
  • Amalgamating chunks of data arriving through pipes from another process is not optional - console logs from the child process came through separately.
Ad
source: stackoverflow.com
Ad