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.
The syntax: // works only inside async functions let value = await promise; The keyword await makes JavaScript wait until that promise settles and returns its result.
Async/Await is used to work with promises in asynchronous functions. It is basically syntactic sugar for promises. It is just a wrapper to restyle code and make promises easier to read and use. It makes asynchronous code look more like synchronous/procedural code, which is easier to understand.
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.
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
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.stdout
and stderr
content from their pipes, and getting the exit code can be done in no particular order since retrieval is asynchronous.Its important to understand that async/await
and promises are the same, just different syntax.
So every async function returns a promise!
so assuming you have a function returining a promise:
function foo() {
return new Promise(resolve => setTimeout(() => resolve("done"), 1000));
}
there are two ways to consume the value.
Promise style:
function test() {
foo().then(value => console.log(value));
}
or async await:
async function test() {
const value = await foo();
console.log(value);
}
Now its important to understand that your original callToolsPromise
function is not promise style. when working with promises you never call new Promise
. Basically the entire idea of of new Promise
is to convert asynchronous non-promise code (and so non-async/await as its the same) to Promises (and so async/await).
Now asynchronous does not mean async/await
but a more general concept. The other common way to handle asynchronity are callbacks.
So tool.on('exit', (code) => {
is asynchronous, but neither a Promise nor async/await.
So the wrapping new Promise
is basically used to convert this to a Promise style function that can be used as Promise or with async/await.
a last word about this snippet:
for await (const data of tool.stdout) {
return data
}
this is a bit problematic. While node streams are async generators, this will return after the first chunk and break as soon as the stream receives multiple chunks.
So you either should replace return
by yield
and return an async generator, or concatenate the buffers with the loop and return
after the loop completes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With