MadPhysicist MadPhysicist - 4 months ago 7
Javascript Question

Javascript Callback Called Before Parent Function

I am writing a short script using Node and Commander. It will display the last few lines of a given file. I have been trying to use the asynchronous file reading in Node, but am having a terrible trouble setting up callbacks. It appears that the code in the callback is executed before the parent function. Is that even possible?

Here is the script:

#!/usr/bin/env node

const readline = require('readline');
const fs = require('fs');
const program = require('commander');
const chalk = require('chalk');

var consoleArguments = process.argv;

var fileValue ='';
var linesArray = [];

var main = function() {

}

function readFile(fileValue, callback){
fs.readFile(fileValue, function(err, data) {
console.log("Hello from fs.readFile callback! \nReading file: " + fileValue);
var i = 0;
if(err) throw err;
linesArray = data.toString().split("\n");
console.log("Hello from fs.readFile callback! Have just read the contents of file.\nThere are " + linesArray.length + " lines in the file!");
for(i = 0; i <= linesArray.length - 1; i++) {
//console.log(i +": " + linesArray[i]);
}
console.log("Returned linesArray from fs.readFile callback!");
return linesArray;
});
console.log("Type of callback: " + typeof(callback));
if (typeof callback === "function") {
// Call it, since we have confirmed it is callable‚Äč
callback(linesArray);
console.log("Called callback");
}
}



program
.version('0.0.1')
.arguments('<file>')
.usage('[options] <file>')
.option('-n, --number <number>', 'Display the last number of lines')
.option('-f, --follow', 'Follow the file and display the last 10 lines as new lines are appended')
.action(function(file) {
fileValue = file;
})
.parse(process.argv);

if(!program.args.length) {
program.help();
} else {
//console.log('Keywords: ' + program.args);
console.log("File: " + fileValue);

if (program.number && program.follow) {
console.log(' - follow');
console.log(' - number');
console.log('Number passed to number option: ' + program.number);

} else {

if (program.number) {
console.log(' - number');
console.log('Number passed to number option: ' + program.number);

console.log(readFile(fileValue, function(linesArray){
console.log("Hello from readFile callback! \nAbout to output file lines!");
var i = 0;
var count = linesArray.length;
var totalString = "";

console.log(count);

for (i = count - 11; i <= count - 1; i++) {
totalString += linesArray[i];
totalString += '\n';
console.log(linesArray[i]);
}

}));
}

if (program.follow) {
console.log(' - follow');
}
}
}

if (program.args === null) program.help();


And here is the baffling (at least to me) part, the output. In short, I have been trying to set up so that a callback outputting the needed lines is called when the file reading was finished.

File: Word_List.txt
- number
Number passed to number option: 10
Type of callback: function
Hello from readFile callback!
About to output file lines!
0
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
undefined
Called callback
undefined
Hello from fs.readFile callback!
Reading file: Word_List.txt
Hello from fs.readFile callback! Have just read the contents of file.
There are 235887 lines in the file!
Returned linesArray from fs.readFile callback!


Edit1:

function writeLines(linesArray){
console.log("Hello from writeLines callback! \nAbout to output file lines!");
var i = 0;
var count = linesArray.length;
var totalString = "";

console.log(count);

for (i = count - 11; i <= count - 1; i++) {
totalString += linesArray[i];
totalString += '\n';
console.log(linesArray[i]);
}
}

function readFile(fileValue, writeLines){
fs.readFile(fileValue, function(err, data, writeLines) {
console.log("Hello from fs.readFile callback! \nReading file: " + fileValue);
var i = 0;
if(err) throw err;
linesArray = data.toString().split("\n");
console.log("Hello from fs.readFile callback! Have just read the contents of file.\nThere are " + linesArray.length + " lines in the file!");
for(i = 0; i <= linesArray.length - 1; i++) {
//console.log(i +": " + linesArray[i]);
}
writeLines(linesArray);
});
}


Edit 2:
Here is the output of Edit1

File: Word_List.txt
- number
Number passed to number option: 10
/usr/local/lib/node_modules/test/NodeTail.js:44
writeLines(linesArray);
^

TypeError: writeLines is not a function
at readFile (/usr/local/lib/node_modules/test/NodeTail.js:44:5)
at Object.<anonymous> (/usr/local/lib/node_modules/test/NodeTail.js:77:20)
at Module._compile (module.js:425:26)
at Object.Module._extensions..js (module.js:432:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:313:12)
at Function.Module.runMain (module.js:457:10)
at startup (node.js:138:18)
at node.js:974:3

Answer

fs.readFile is async method which means that you will read the file when he is ready, so the rest of the code can be processed until the read is ready, that is way your callback is called before the file finish being read.

You have to options:

  1. put the callback function inside the fs.readFile callback at the end.
  2. you can use fs.readFileSync which will run you code synchronously so the callback method will be invoked only after you finsihed reading the file.

I think the first option is better because that's the way Node.js works.