Understanding function (err, data) callbacks

2019-02-19 23:20发布

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);

4条回答
Lonely孤独者°
2楼-- · 2019-02-19 23:38

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.

查看更多
虎瘦雄心在
3楼-- · 2019-02-19 23:38

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);
    }
});
查看更多
Viruses.
4楼-- · 2019-02-19 23:52

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]
查看更多
疯言疯语
5楼-- · 2019-02-19 23:53

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);
});
查看更多
登录 后发表回答