user3728233 user3728233 - 1 month ago 16
Javascript Question

Node.js order of execution of middleware and callback functions?

I am fairly new to node.js, I have a starting point as

app.js
, It calls a file called
xmlParser.js
which parses an input xml file using the
xml2js
node module. This is what
app.js
looks like,

//include the modules
var express = require('express'),
app = express(),
ejs = require('ejs'),
xmlParser = require('./utils/xmlParser.js');

//save the pain of writing .ejs after each render
app.set("view engine","ejs");

//these will hold the result of the function in the file xmlParser
var listJSON, logJSON = {};

app.get('/portfolio', function(req, res, next){
//call parse function in file xmlParser.js
xmlParser.parse(__dirname + "/xml/svn_log.xml", listJSON);
xmlParser.parse(__dirname + "/xml/svn_list.xml", logJSON);
console.log("middleware was excuted!");
next();
}, function(req, res){
console.log(listJSON);
});


Now this is how the file
xmlParser.js
looks like,

//include the modules
var fs = require('fs'),
xml2js = require('xml2js'),
parser = new xml2js.Parser();

//this function parse xml file with name as filename to json and stores it in variable outputJSON
function parse(filename, outputJSON){
fs.readFile(filename, function(err, data){
if(err){
console.log('cannot read file.');
} else {
parser.parseString(data, function(err, result){
if(err) console.log('cannot parse file.');
else {
outputJSON = result;
}
});
}
});
}

module.exports = {
parse
}


Now when I run the application and go to route
/portfolio
I expect this to be printed:

middleware was executed!
[object]


But this is what I'm getting on my node console,

middleware was executed!
undefined


Now, What I want is the function in
xmlParser.js
executes and stores the parsed JSON object in the
listJSON
object which I pass to it. When I pass
listJSON
as the second argument to
parse()
function, is it pass by reference? Why am I getting 'undefined
printed when my function in the file
xmlParser.js` stores an object in it?

Answer

One problem is that your parse function calls fs.readFile and parser.parseString which are asynchronous. Second problem is that you change the object in your function: JavaScript passes parameters by object sharing. Meaning: primitive variables are passed by value: changing the parameters doesn't change the source variable. Non primitive variables like objects and arrays, cannot be changed either, but their properties can.

While indeed you call parse before you call next, asynchronosly reading the file, and then asynchronously parsing it, (without blocking your route code), makes next come sooner than the program finishes parsing the file.

What you can do is pipe your asynchrnous actions one by one, also make the parse return the result and you will set it after. First change your parse function to accept a callback and call it:

function parse(filename, callback){
    fs.readFile(filename, function(err, data){
        if(err){
            console.log('cannot read file.');
            return callback(err);
        } else {
            parser.parseString(data, function(err, result){
                if(err) {
                   console.log('cannot parse file.');
                   return callback(err);
                }
                else {

                    return callback(null, result);
                }
            });
        }
    });
}

Then, pipe your parse functions together with the next function:

app.get('/portfolio', function(req, res, next){
    xmlParser.parse(__dirname + "/xml/svn_log.xml", function(err,  list) {
      listJSON = list;
      xmlParser.parse(__dirname + "/xml/svn_list.xml", function(err, log) {
        logJSON = log;
        next();
      });
    });
    console.log("middleware was excuted!");
}, function(req, res){
    console.log(listJSON);
});
Comments