Reputation: 1706
So I am confused about how the function(err, data) callback works, is the first argument always an error handler?
What about the remaining arguments if you had something like function (x, y, z, a, b, c)?
How does the data from fs.readFile pass from the top line of code to the bottom line of code? Or in other words, how does the output of fs.readFile get put into the data argument?
fs.readFile(pathToFile, function (err, **data**) {
bufferString = **data**.toString();
I could replace function (err, data) with function (x, y) and function (x, y, z, a, b, c)
But only the second argument works (data and y), is this just the syntax of javascript callbacks?
For example, this is working code to asynchronously read a file and print out the number of lines given a file:
var fs = require('fs');
var pathToFile = process.argv[2];
var bufferString, bufferStringSplit;
function counter(callback) {
fs.readFile(pathToFile, function (err, data) {
bufferString = data.toString();
bufferStringSplit = bufferString.split('\n');
callback();
});
}
function logMyNumber() {
console.log(bufferStringSplit.length-1);
}
counter(logMyNumber);
Upvotes: 6
Views: 23999
Reputation: 140220
When you call a function it can either return a single value or throw a single error.
So the "equivalent" of this synchronous code:
try {
var data = functionCall();
//Use data
}
catch(err) {
console.log(err);
}
Is this asynchronous callback:
functionCallAsync(function(err, data) {
if (err) {
console.log(err);
}
else {
//Use data
}
});
You can see now why it makes no sense to use multiple values in the callback. In fact, such pattern is actively harmful.
For example when using generators you are back to only having the possibility of using a single return value. But if some ignorant library author went ahead and broke the callback convention and used multiple, this would be impossible.
async(function* () {
try {
//What if `functionCallAsync` was called back with multiple values?
var data = yield functionCallAsync();
//Use data
}
catch(e) {
console.log(e);
}
});
Upvotes: 1
Reputation: 102753
Easy to understand with an example... you can write your own version of a function that uses a callback, quite simply:
function getTwoStringsAsync(callback) {
setTimeout(function() {
callback("foo", "bar");
}, 1000);
}
Here, the function getTwoStringsAsync
is pre-supposing that "callback" is a function, and it implies signature (two parameters that are strings). The consumer of your method is free to adhere to the implied signature or not. It can use one or both of those values, or neither. But if it assumes any more than two parameters, those will appear as undefined.
getTwoStringsAsync(function() { console.log("done!"); });
getTwoStringsAsync(function(first, second) { console.log("got " + first + ", " + second); });
getTwoStringsAsync(function(first, second, third) { console.log(third); }); // "undefined"
It is arguably a limitation, or a strength, or weakly-typed languages like Javascript, that function signatures will not be verified. A strongly-typed language may generate a compiler error or warning in the first or third uses above.
Upvotes: 3
Reputation: 707318
The caller of the callback (which is the readFile
method in this case) decides what arguments are passed to the callback. You need to declare your callback to match what readFile
says that it will pass to the callback. You can name the arguments anything you want (the names you use do not matter), but they will get values in the order that readFile
decides.
In this case, fs.readFile()
calls the callback with the two arguments you have in your code as in callback(err, data)
.
Here's an example from the node.js docs:
fs.readFile('/etc/passwd', function (err, data) {
if (err) throw err;
console.log(data);
});
Upvotes: 6
Reputation: 94101
The arguments of the callback depend on how the callback gets called. For example:
var map = function(xs, f) {
var result = [];
for (var i=0; i<xs.length; i++) {
// Here we call the callback function
// with the element as context, the element as first param
// and the index as second param.
// Extra parameters are `undefined`
result.push(f.call(xs[i], xs[i], i));
}
return result;
};
Then you'd use it like:
var addIdx = function(x,i){ return x+i };
map([1,2,3], addIdx); //=> [1,3,5]
Upvotes: 1