Reputation: 147
I'm having a lot of trouble wrapping my head around the asynchronous nature of code execution on NodeJS. I have a simple function to fetch the output of ip a
on a Linux machine and parse out the IP Subnet manually. I'd simply like to console.log()
the IP Subnet after that's done.
I understand that NodeJS mostly runs asynchronously so I can't expect the logic to be finished before I console.log()
the variable. I understand to concept of Callbacks to combat this problem, but I'd prefer to have access to the variable outside the logic loop. I turned to Promises, which seems like a good solution for this, but I think I'm missing something and they're not working the way I expected. Here's my code below:
let subnetString = '';
function getIPLinux() {
return new Promise((resolve) => {
const ipOutput = spawn( 'ip', ['a'] );
ipOutput.stdout.on('data', (data) => {
String(data).split('\n').forEach( (line) => {
if ( line.includes('inet') && line.indexOf('inet6') < 0 && line.indexOf('127.0.0.1') < 0 ) {
const ipSubnet = line.split(' ')[5];
const ipStringArray = ipSubnet.split('.');
subnetString = ipStringArray[0] + '.' + ipStringArray[1] + '.' + ipStringArray[2] + '.*';
console.log('Found subnet at end of if loop: ' + subnetString);
}
})
})
console.log('Found subnet at end of promise: ' + subnetString);
resolve();
})
}
getIPLinux().then( () => {
console.log('Found subnet after then: ' + subnetString);
});
My output is as follows:
Found subnet at end of promise:
Found subnet after then:
Found subnet at end of if loop: 192.168.1.*
Only the last line logged is correct. I'm having trouble wrapping my mind around this non-blocking code execution. I'm open to other methodologies too if I'm coming at this the wrong way.
Upvotes: 0
Views: 139
Reputation: 4116
resolve
your Promise
from inside the .on('data', (data) => { // here })
callbacksubnetString
as a global variable, pass it inside the Promise
. Like this resolve(subnetString)
catch
if there's any errors. For example if no valid lines have been foundsubnetString
you can use Array.find
instead of doing a forEach
loop. It take a "validation" callback and returns the item or null
if not foundconst isValidLine = l => l.includes('inet') && !l.includes('inet6') && !l.includes('127.0.0.1');
const extractSubnet = l => {
const ipStringArray = l.split(' ')[5].split('.');
return ipStringArray[0] + '.' + ipStringArray[1] + '.' + ipStringArray[2] + '.*';
}
function getIPLinux() {
return new Promise((resolve, reject) => {
const ipOutput = spawn('ip', ['a']);
ipOutput.stdout.on('data', data => {
const line = data.split('\n').find(isValidLine);
// Resolve or Reject your promise here
if (!line) {
reject('Line not found');
} else {
const subnetString = extractSubnet(line);
resolve(subnetString);
}
});
});
}
getIPLinux().then(subnetString => {
console.log('Found subnet after then: ' + subnetString);
});
Upvotes: 1
Reputation: 222309
This is a problem similar to this one that wasn't correctly addressed with promises.
resolve
is synchronously called on promise construction, a promise resolves earlier than data arrives. A promise is supposed to resolve with data but it doesn't.
Considering that only single data
should be processed, it should be:
function getIPLinux() {
return new Promise((resolve) => {
const ipOutput = spawn( 'ip', ['a'] );
const handler = (data) => {
String(data).split('\n').forEach((line) => {
if (line.includes('inet') && line.indexOf('inet6') < 0 && line.indexOf('127.0.0.1') < 0) {
const ipSubnet = line.split(' ')[5];
const ipStringArray = ipSubnet.split('.');
const subnetString = ipStringArray[0] + '.' + ipStringArray[1] + '.' + ipStringArray[2] + '.*';
ipOutput.stdout.off('data', handler);
resolve(subnetString);
}
})
})
ipOutput.stdout.on('data', handler);
})
}
Upvotes: 0
Reputation: 135
spawn() is also async. You're using an event callback for the stdout which is good but you're resolving the promise immediately below that without waiting for the input to finish. Try
ipOutput.on('close', () => {
console.log('Found subnet at end of promise: ' + subnetString);
resolve(subnetString);
});
At the end of your promise.
https://nodejs.org/api/child_process.html#child_process_event_close
Upvotes: 2