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
Related
Kyle Simpson has an amazing class on pluralsight.
In one of the modules, he goes through a snippet of code that can be safely called asynchronously, and be certain that the results are going to be shown to the user in the same sequence with which the code was executed.
The function in its glory:
function getFile(file) {
var text, fn;
fakeAjax(file, function(response){
if (fn) fn(response);
else text = response;
});
return function(cb) {
if (text) cb(text);
else fn = cb;
}
}
We can call it like so:
I'm having a tough time understanding the getFile function:
where is cb defined? how does it get called, i.e. cb(text) if it's not defined anywhere?
when we call getFile, how does the response get a value at all?
getFile returns an anonymous function:
return function(cb) {
if (text) cb(text);
else fn = cb;
}
so var th1 = getFile("file") ends up assigning that anonymous function to the value of th1, so th1 can now be called with an argument - which becomes cb. So when later, we call th1 with:
th1(function(text1) {
...
we are passing in a second anonymous function (with argument text1) which is assigned to cb (shorthand for 'callback').
The reason it works is that when the ajax is complete, it does one of two things:
if fn is defined, calls fn with the response
if not, it stores the response
Conversely, when the returned anonymous function is called, it does one of two things:
if text is defined (i.e. a result is already received) then it calls the callback with the response
if not, it assigns the callback (cb) to fn
This way, whichever happens first - ajax complete, or thunk called, the state is preserved, and then whichever happens second, the outcome is executed.
In this way, the 'thunks' can be chained to ensure that while the ajax functions happen in parallel the output methods are only called in the sequence in which the fn values are assigned.
I think part of the confusion is unclear variable naming, and the use of liberal anonymous functions with out giving them an intention revealing name. The following should be functionally equivalent with clearer naming (I think):
function getFile(file) {
var _response, _callback;
fakeAjax(file, function(response){
if (_callback) _callback(response);
else _response = response;
});
var onComplete = function(callback) {
if (_response) callback(_response);
else _callback = callback;
}
return onComplete;
}
then:
var onFile1Complete = getFile("file1");
var onFile2Complete = getFile("file2");
var onFile3Complete = getFile("file3");
var file3Completed = function(file3Response) {
output("file3Response");
output("Complete!");
}
var file2Completed = function(file2Response) {
output(file2Response);
onfile3Complete(file3Completed)
}
var file1Completed = function(file1Response) {
output(file1Response);
onFile2Complete(file2Completed);
}
onFile1Complete(file1Completed);
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)
}
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"
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 });
I have some code that requests some JSON from an API. When data is returned, per the documentation, it sends back a callback function that is to be used to parse the data at the top level. After the call is made, I have the following code to capture the data and process it:
var callback = 'functionUsedInApiCall';
window[callback] = newCallBackFunction;
How would I go about passing custom params to the callback function above as the data is being returned?
In order to capture the data, I must write the callback function like this:
function newCallBackFunction(root) {
//root is the data
}
Any help would be greatly appreciated.
Are you talking about JSONP? If so, you don't call the callback or pass in the argument at all, the code returned by the API does.
E.g., your code:
window.myCallback = newCallbackFunction;
function newCallbackFunction(data) {
// use the data
}
(I'm assuming this isn't at global scope, hence assigning to the window object.)
...plus your code for initiating the JSONP call, which is usually appending a script element to your page with a URL containing the name of the callback ("myCallback" in the above).
Their response will look like this:
myCallback({
// data here
});
...which, when it arrives, will run (because it's the content of a script element), and will call your function. This is how JSONP works.
If you want to include further arguments for the function, all you do is have the callback they call turn around and call your target function, e.g.:
window.myCallback = function(data) {
newCallbackFunction(data, "foo", "bar");
};
function newCallbackFunction(data) {
// use the data
}
Now when their code calls the global myCallback, all it does is turn around and call newCallbackFunction with that data and the arguments you specify.
Those arguments don't have to be literals as in the above. Here's an example with a bit more context, using a closure:
// Assume the url already contains the name of the callback "myCallback"
function doJSONP(url, niftyInfo, moreNiftyInfo) {
var script;
// Set up the callback
window.myCallback = function(data) {
// Runs when the data arrives
newCallbackFunction(data, niftyInfo, moreNiftyInfo);
};
// Trigger the request
script = document.createElement('script');
script.src = url;
document.documentElement.appendChild(script);
}
Ideally, though, when doing JSONP you auto-generate the name of the callback each time so that it's specific to the request (in case you have two outstanding requests at the same time):
// Assume the url ends with "callback=" and we append the name of the
// callback function to it
function doJSONP(url, niftyInfo, moreNiftyInfo) {
var cbname, script;
// Get a callback name
cbname = "callback_" +
new Date().getTime() +
"_" +
Math.floor(Math.random() * 10000);
// Set up the callback
window[cbname] = function(data) {
// Remove us from the window object
try {
delete window[cbname];
}
catch (e) { // Handle IE bug (throws an error when you try to delete window properties)
window[cbname] = undefined;
}
// Runs the function
newCallbackFunction(data, niftyInfo, moreNiftyInfo);
};
// Trigger the request
script = document.createElement('script');
script.src = url + encodeURIComponent(cbname);
document.documentElement.appendChild(script);
}
Parameters in javascript are passed as an Array, so you can pass the parameters you need, or even complete functions that will add per case functionality in your callback.
You could do the following:
function newCallBackFunction(root /*your data*/, paramsHash /*a hash array with optional parameters*/)
{
//if arg1 can be found in the hash
if( paramsHash['arg1'] ]
{
//do something that requires arg1
}
else if( paramsHash['arg2'] )
{
//do something that requires arg2
}
return root;
}
And in your main code:
var hash = new Array();
hash['arg1'] = 'str1';
hash['arg2'] = 1;
hash['arg3'] = new Car(); //or any other object you want
It is also possible to just declare some parameters and supply them to your function only when needed:
function newCallBackFunction(root, param1, param2)
{
if( param1 ) { /* similar to first example */ }
}
Or finally just pass whatever parameter you want and read them from the arguments table
function newCallBackFunction(root)
{
for( int i = 1; i < arguments.length; i++ )
//do something with the parameters you pass beside root
}
And in main code:
newCallBackFunction( root, param1, param2, param3 );
I hope I covered you!