22

Node programmers conventionally use a paradigm like this:

let callback = function(err, data) {
  if (err) { /* do something if there was an error */ }
  
  /* other logic here */
};

Why not simplify the function to accept only a single parameter, which is either an error, or the response?

let callback = function(data) {
  if (isError(data)) { /* do something if there was an error */ }
  
  /* other logic here */  
};

Seems simpler. The only downside I can see is that functions can't return errors as their actual intended return value - but I believe that is an incredibly insignificant use-case.

Why is the error-first pattern considered standard?

EDIT: Implementation of isError:

let isError = obj => obj instanceof Error;

ANOTHER EDIT: Is it possible that my alternate method is somewhat more convenient than node convention, because callbacks which only accept one parameter are more likely to be reusable for non-callback use-cases as well?

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • 2
    How would you propose to reliably check for an error in `isError`? – Justin Niessner Nov 09 '16 at 16:20
  • Edited to include `isError` implementation! – Gershom Maes Nov 09 '16 at 16:21
  • [Understanding Error-First Callbacks](http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/) – aring Nov 09 '16 at 16:56
  • 1
    Thanks aring, although I completely understand the use of error-first callbacks! I'm just interested in how such a format became well-accepted convention. – Gershom Maes Nov 09 '16 at 16:58
  • @GershomMaes, in that article is not only about it's use. It's also about simplicity. – aring Nov 09 '16 at 17:00
  • 1
    @GershomMaes, answering about your method: In my opinion, your method is at least slower. – aring Nov 09 '16 at 17:04
  • @GershomMaes I published a module on npm that lets you use your style of callbacks with standard Node async functions like `fs.readFile` etc. See the update to [my answer below](https://stackoverflow.com/questions/40511513/why-does-node-prefer-error-first-callback/40512067#40512067) for details. Comments welcome. – rsp Nov 09 '16 at 18:26

3 Answers3

23

(See "Update" below for an npm module to use the callback convention from the question.)

This is just a convention. Node could use the convention that you suggest as well - with the exception that you wouldn't be able to return an error object as an intended value on success as you noticed, which may or may not be a problem, depending on your particular requirements.

The thing with the current Node convention is that sometimes the callbacks may not expect any data and the err is the only parameter that they take, and sometimes the functions expect more than one value on success - for example see

request(url, (err, res, data) => {
  if (err) {
    // you have error
  } else {
    // you have both res and data
  }
});

See this answer for a full example of the above code.

But you might as well make the first parameter to be an error even in functions that take more than one parameter, I don't see any issue with your style even then.

The error-first Node-style callbacks is what was originally used by Ryan Dahl and it is now pretty universal and expected for any asynchronous functions that take callbacks. Not that this convention is better than what you suggest or worse, but having a convention - whatever it is - make the composition of callbacks and callback taking functions possible, and modules like async rely on that.

In fact, I see one way in which your idea is superior to the classical Node convention - it's impossible to call the callback with both error and the first non-error argument defined, which is possible for Node style callbacks and sometimes can happen. Both conventions could potentially have the callback called twice though - which is a problem.

But there is another widely used convention in JavaScript in general and Node in particular, where it's impossible to define both error and data and additionally it's impossible to call the callback twice - instead of taking a callback you return a promise and instead of explicitly checking the error value in if as is the case in Node-style callbacks or your style callbacks, you can separately add success and failure callbacks that only get relevant data.

All of those styles are pretty much equivalent in what they can do:

nodeStyle(params, function (err, data) {
  if (err) {
    // error
  } else {
    // success
  }
};

yourStyle(params, function (data) {
  if (isError(data)) {
    // error
  } else {
    // success
  }
};

promiseStyle(params)
  .then(function (data) {
    // success
  })
  .catch(function (err) {
    // error
  });

Promises may be more convenient for your needs and those are already widely supported with a lot of tools to use them, like Bluebird and others.

You can see some other answers where I explain the difference between callbacks and promises and how to use the together in more detail, which may be helpful to you in this case:

Of course I see no reason why you couldn't write a module that converts Node-style callbacks into your style callbacks or vice versa, and the same with promises, much like promisify and asCallback work in Bluebird. It certainly seems doable if working with your callback style is more convenient for you.

Update

I just published a module on npm that you can use to have your preferred style of callbacks:

You can install it and use in your project with:

npm install errc --save

It allows you to have a code like this:

var errc = require('errc');
var fs = require('fs');

var isError = function(obj) {
  try { return obj instanceof Error; } catch(e) {}
  return false;
};

var callback = function(data) {
  if (isError(data)) {
    console.log('Error:', data.message);
  } else {
    console.log('Success:', data);
  }
};

fs.readFile('example.txt', errc(callback));

For more examples see:

I wrote this module as an example of how to manipulate functions and callbacks to suit your needs, but I released it under the MIT license and published on npm so you can use it in real projects if you want.

This demonstrates the flexibility of Node, its callback model and the possibility to write higher-order functions to create your own APIs that suit your needs. I publish it in hope that it may be useful as an example to understand the Node callback style.

Community
  • 1
  • 1
rsp
  • 107,747
  • 29
  • 201
  • 177
8

Because without this convention, developers would have to maintain different signatures and APIs, without knowing where to place the error in the arguments array.

In most cases, there can be many arguments, but only one error - and you know where to find it.

Joyent even wrote about this at the time they were more involved:

Callbacks are the most basic way of delivering an event asynchronously. The user passes you a function (the callback), and you invoke it sometime later when the asynchronous operation completes. The usual pattern is that the callback is invoked as callback(err, result), where only one of err and result is non-null, depending on whether the operation succeeded or failed.

GrafiCode
  • 3,307
  • 3
  • 26
  • 31
LifeQuery
  • 3,202
  • 1
  • 26
  • 35
1

Yeah we can develop code style as you said. But there would be some problems.If we maintain code style what we want , different signatures of API increases and of course there would be dilemma between developers. They create their layers( error and success stages for example) again. Common conventions play an important role in spreading best practices among developers.

Ingenral, the error-first Node-style callbacks is what was originally used by Ryan Dahl and it is now pretty universal and expected for any asynchronous functions that take callbacks. Not that this convention is better than what you suggest or worse, but having a convention - whatever it is - make the composition of callbacks and callback taking functions possible, and modules like async rely on that.