Passing additional parameters to the callback of fs.readFile in Node js - javascript

Is it possible to pass additional parameters to the callback function of a fs.readFile. I have my code to read a directory and parse all the XML documents. I need to pass the filename down the callback chain for additional processing. As of now my code is
var fs = require('fs');
var path = require('path');
module.exports.extractXMLBody = function (dirPath, ext) {
fs.stat(dirPath, function (err, stats) {
if (stats.isDirectory()) {
fetchFiles(dirPath, ext, function (listOfFiles) {
_.each(listOfFiles, function (val, key) {
var completePath = dirPath + '/' + val;
var fileName = path.basename(val, path.extname(val))
// TODO : Figure out to pass additional parameters
fs.readFile(fullPath, parseXML);
});
});
}
});
}
Here parseXML is the callback i have defined as a separate function & I want to pass the variable newFileName to the callback function parseXML.
Note : If i write the callback as anonymous function, i can pass the access the variable but I'm trying to avoid the further nesting of callbacks.

No, you can't have additional values passed to the callback function since that is not supported by fs.readFile(). It will only pass back 2 values to the callback function at all times. It follows an error-first callback structure, so that means the first parameter will be an Error or null if one didn't occur and the next argument will contain the data of the file, if there is any.
If you want to access the fileName variable inside your callback for fs.readFile() then you would need to define it within the same scope where fs.readFile() is called. This would more or less require you to go with an anonymous function. However you should still name it for better Error StackTraces.
_.each(listOfFiles, function (val, key) {
var completePath = dirPath + '/' + val;
var fileName = path.basename(val, path.extname(val));
fs.readFile(fullPath, function parseXML(err, data) {
if(err) {
console.log(err);
}
else {
// parseXML and still have access to fileName
}
});
});

Actually, there's a way for you to do it now. You can use the .bind function to pass down a variable to your callback function.
Eg:
for (file of files) {
fs.readFile(`./${file}`, 'utf8', gotFile.bind({ "filename": file }))
}
And in your callback function (gotFile in my example), you van use the variable filename as such:
console.log(`Read ${this.file}.json`)

As #Aditya commented, .bind() solves the problem but the provided code didn't work for me so I got it to work like this:
fs.readFile(fullPath, parseXML.bind(null, additionalParameter))
const parseXML = (additionalParameter, error, fileContents) => {
console.log(additionalParameter)
}

Related

node: mocking a function with a callback argument

I am trying to write unit tests for a function that reads a jsonfile into an object. I read the file with
jsonfile.readFile(filename, function (err, obj) {
//...
});
For my unit tests, I want to mock this function so that, rather than actually reading the file, it will simply return a fixed json block and pass it into the callback.
What I'm having trouble with is how to mock the function. I've seen sinon, which says it supports mocking functions, but I can't find anything that describes how to actually define custom behavior for the function I'm mocking. Sinon looks like it allows me to define what I want the function to return, how often I expect it to be called, etc, but not actually define a mocked function.
Basically, I want something like this:
mock(jsonfile, 'readFile', function(filename, callback) {
callback(null, {attr1:"foo"});
});
How do I do this with sinon?
But actually, why don't you just replace readFile by a function with the same definition (so that it doesn't break the code using it). And just return your mock data.
jsonfile.readFile = function(filePath, callback) {
callback(null, { mockData: "foo" });
};
easy as that.
Otherwise, you can use a Proxy if you don't want to deal with the definition :
const jsonfile = {
readFile: function(filename, callback) {
callback();
}
};
// intercept every call to readFile and always return the mock data
jsonfile.readFile = new Proxy(jsonfile.readFile, {
apply: function(target, thisArg, args) {
return args[1](null, { someMocking: "" });
}
});
// call readFile as usual
jsonfile.readFile('testfile', function(err, result) {
console.log(result);
});
Proxies work as interceptors.
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy
This is not straightforward in testing because it involved callbacks. You need to test wether a callback you passed to readFile was called with right arguments, which in this case is the dummyFile.
import sinon from 'sinon'
import jsonfile from './jsonfile'
const jsonFileMock = sinon.spy(jsonfile, 'readFile');
const callback = sinon.spy();
jsonfile.readFile(callback);
jsonFileMock.callsArgWith(1, 'dummyFileName');
expect(callback.calledWith('dummyFileName')).to.be.true;
jsonFileMock.restore();
If you want to abstract this into a function, than it can be something like :
function mock(module, method, ...callbacks){
const stub = sinon.stub(jsonfile, 'readFile');
callbacks.forEach((callback, index) => {
stub.callsArgWith(index, callback);
});
}
The function I was looking for is stub.callsFake():
> Thing = {
... meth : function() { console.log(1) }
... }
> Thing.meth()
1
> var stub = sinon.stub(Thing, 'meth')
> stub.callsFake(function() { console.log(2) })
> Thing.meth()
2
> stub.restore()
> Thing.meth()
1
It doesn't look like mock is capable of what I want to do.

Callback without parameter in javascript

I have a nodejs code that has callback and I couldn't understand how it works. Can someone explain it
function readJSONIntoArray(directory, array, callback)
{
var ending = 'json';
fs.readdir(directory, function (err, files)
{
if (err)
throw err;
var fileCnt = files.length;
files.forEach(function (file)
{
if (endsWith(file, '.' + ending))
{
file = file.substring(0, file.length - (ending.length + 1));
var fileContent = require(path.join(directory, file));
array.push(fileContent);
log.info('Read file: ' + file);
}
fileCnt--;
if (fileCnt === 0 && typeof callback === 'function')
{
callback();
}
});
});
}
Here the callback is empty so I guess no value is being returned. But in actual output the array is returned. I couldn't understand an empty callback can return a array.
Function call:readJSONIntoArray(profilefolder, profiles, setProfileDescriptions);
Definition of setProfileDescriptions is separate.
function setProfileDescriptions()
{
profiles = bubblesort(profiles, 'order');
}
Inside the setProfileDescriptions the profile array is populated with the json data from the file read in the read function.
Can someone explain how the 3rd argument in the readJSONIntoArray function call is recognized as a function and the array profiles is returned?
You're right that readJSONIntoArray does't return anything in it's callback. Instead it appends new data to the second argument array, thus mutating it.
So, readJSONIntoArray was meant to be used in the following way:
var content = []; // empty array to accumulate data from readJSONIntoArray function
readJSONIntoArray('some directory', content, function () {
// content is full of data now
doSomething(content);
});
Though I must point out that this is not a common pattern in node.js, and that it should be avoided because it's too confusing.
In fact, there are several things in readJSONIntoArray implementation which were done wrong:
functions should never mutate their arguments;
async functions should not throw errors, they should return them in callback instead;
any data produced by the function should also be returned in callback.
var globalArray=[];
function readFunction(path,globalArray,callbackFunction){
globalArray.push(path);
callbackFunction();
}
function callbackFunction(){
//globalArray was global so i can call here
console.log(globalArray);
}
readFunction('filePath',globalArray,callbackFunction);
consider above code because the 'globalArray' declared as global i can access inside the callback function

nodejs callback don't understand how callback result come through argument

Seen and run code below, thought I understand closures... How that 'avatarUrl' in callback argument is received in 'avatar'which is function argument too. I know this is common pattern, just can't get it yet
var GitHubApi = require('github');
var github = new GitHubApi({
version: '3.0.0'
});
var getUserAvataWithCallback = function(user, callback) {
github.search.users({q: user}, function(err,res) {
if (err) { callback(err, null);}
else {
var avatarUrl = res.items[0].avatar_url;
callback(null, avatarUrl);
}
});
};
getUserAvataWithCallback('irom77', function(err,avatar) {
console.log('got url with callback pattern', avatar);
})
So, callbacks are an underlying and integral concept in javascript, so it is important that you understand a few concepts. Look at this example:
// This is the function definition for "foo"
//here the callback argument refers to
//the second argument in the function call at
//the bottom which is a function
var foo = function(arg, callback) {
if(arg%2 != 0)
callback("arg is odd", arg)
else
callback(null, arg)
}
//Function call to foo
foo(2, function(err, num) {
if(err)
console.log(err)
else
console.log(num)
}
So, in the example above, you can think of the function call as a call with two parameters, the integer 2 and a function.
In the function definition:
The integer is referred to as "arg" and the function is referred to as "callback".
When callback("arg is odd", arg) is executed, the function is called with:
err = "arg is odd"
num = arg
When callback(null, arg) is executed, the function is called with:
err = null
num = arg
The important thing to remember here is that in javascript, functions can be passed as arguments to other functions. Do some further reading here.
The name of the argument passed to a function does not need to be the name of the argument in the definition of the function, the argument in the definition is the name of the variable that will be initialized inside the scope of given function. The argument declaration will receive the value passed at the second position of the function call (as per the code you've provided) and you will be able to access it inside the scope with that name. You can:
function foo(arg1, arg2) {
console.log(arg1, arg2);
}
foo(true, true); // will output true, true
foo(0, 1); //will output 0, 1
foo('shikaka', 1); //will output "shikaka", 1
var bar = "shikaka";
foo(bar, "shikaka"); //will output "shikaka", "shikaka"

Why is this named anonymous js function working before it is defined?

I have a simple Gulpfile with a task defined. There is a named anonymous function that is defined after the Gulp task. The task uses this function and is working when I would expect to get undefined is not a function, but I am not. Here is the code:
gulp.task('bower', function() {
var bowerDir = 'bower_components';
fs.readdir(bowerDir, function(err, dirs) {
_.each(dirs, function(dir) {
var directory = dir;
fs.stat(path.join(bowerDir, dir), function(err, stats) {
if(stats.isDirectory()) {
listing('bower_components');
}
});
});
});
});
var listing = function(dir) {
console.log(dir);
};
Please explain why this is working?
gulp.task(), fs.readdir() and fs.stat() are all asynchronous functions. They call their callback functions sometime LATER, not immediately. That means that the code after that defines listing gets a chance to run BEFORE the callback is actually called. So, thus listing is defined before it is actually used.
I wouldn't suggest this as a good coding method myself because you are relying on the timing on things.
If, instead you defined your listing function like this:
function listing(dir) {
console.log(dir);
}
Then, you would not have a dependency on timing because all statically defined functions like this are parsed first and hoisted to the top of the scope they are defined in and thus always available in that scope ragardless of timing.
FYI, if you really want to show this to yourself, you can add this logging to see the actual timing and sequence of things:
function logTime(msg) {
console.log((new Date()).getTime() + ": " + msg);
}
logTime("start");
gulp.task('bower', function() {
var bowerDir = 'bower_components';
fs.readdir(bowerDir, function(err, dirs) {
_.each(dirs, function(dir) {
var directory = dir;
fs.stat(path.join(bowerDir, dir), function(err, stats) {
if(stats.isDirectory()) {
logTime("about to call listing()");
listing('bower_components');
}
});
});
});
});
logTime("about to define listing");
var listing = function(dir) {
logTime("listing() called");
console.log(dir);
};
Because that annonymous function is a callback function and most likely is called after the initialization of listing function.

Return object into async.each function in node.js

I want to understand one thing about async module in node.js.
I have created a function that map an object from a form to a model object and return this object.
This object is a video with an array of tags.
My question is where can I return the video ? I know normally it is inside the async callback function but if I do that, the object returned is undefined.
Whereas If i return the video object at the end of the whole function, it works but it's not safe as I'm not sure, my async is finished...
By the way, I don't understand the callback function passed in argument to async.each and
called after video.products.push(tag); . What does this function do?
Regards
in my mapping.js :
exports.video = function(object) {
var video = new Video();
video.name = object.name;
video.products = [];
async.each(object.tags, function(tago, callback) {
tag = {
"name" : tago.name
}
video.products.push(tag);
callback();
} ,
function(err) {
if( err ) {
console.log('Error' + error);
throw err;
}
logger.debug("into async" + video);
}
);
logger.debug("end function " );
**//return video;**
}
in my video.js :
var video = mapping.video(object);
logger.debug(video); // return undefined
The simple answer is that you can't - at least not via easy or obvious approach. As its name suggests, async is a library for queuing up asynchronous function calls into the event loop. So your exports.video function simply kicks off a bunch of asynchronous functions, which execute one after the other on an unpredictable time-frame, and then returns immediately. No matter where you try to return your video object within the scope of your function calls which are instantiated by async, the exports.video function will already have returned.
In this case it doesn't really seem like you need asynchronous function calls for what you're doing. I'd suggest that you replace your use of async with something like Underscore's each method, which executes synchronously, instead.
http://documentcloud.github.io/underscore/#each
You'd need to define a callback for your exports.video function e.g..
exports.video = function(object, callback) {
// video code (snip)...
async.each(object.tags,
function eachTag(tag, done) {
// code run for each tag object (snip)...
done();
},
function finished(err) {
// code run at the end (snip)...
callback(thingThatsReturned);
});
};
...and call it like this:
var videoUtils = require('videoUtils');
var tags = getTags();
videoUtils.video({ tags: tags }, function(thingThatsReturned) {
// do something with 'thingThatsReturned'
});
By the way, I don't understand the callback function passed in
argument to async.each and called after video.products.push(tag); .
What does this function do?
The async.each function will call the 'eachTag' function above (2nd argument) for each item in your array. But because it's done asynchronously, and you might do something else async in the function (hit a database/api etc.), it needs to know when that function for that particular array item has finished. Calling done() tells async.each that the function has finished processing. Once all the functions are finished processing (they've all called done()), async.each will run the 'finished' function above (3rd argument).
This is pretty standard async stuff for Node.js, but it can be tricky to get ones head around it at first. Hang in there :-)
Edit: It looks like your code isn't doing anything asynchronous. If it was, then the above code would be the way to do it, otherwise the following code would work better:
exports.video = function(object) {
// video code (snip)...
if (Array.isArray(object.tags)) {
object.tags.forEach(function eachTag(tag) {
// code run for each tag object (snip)...
});
}
return thingThatsReturned;
};
...and call it...
var videoUtils = require('videoUtils');
var tags = getTags();
var thingThatsReturned = videoUtils.video({ tags: tags });

Categories

Resources