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.
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.js
to 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 thatawait
will wait on an open stream that doesn't have data available at the time. - retrieving
stdout
andstderr
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.
Related Questions
- → How to update data attribute on Ajax complete
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → Octobercms Component Unique id (Twig & Javascript)
- → Passing a JS var from AJAX response to Twig
- → Laravel {!! Form::open() !!} doesn't work within AngularJS
- → DropzoneJS & Laravel - Output form validation errors
- → Import statement and Babel
- → Uncaught TypeError: Cannot read property '__SECRET_DOM_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' of undefined
- → React-router: Passing props to children
- → ListView.DataSource looping data for React Native
- → Can't test submit handler in React component
- → React + Flux - How to avoid global variable
- → Webpack, React & Babel, not rendering DOM