Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ugultopu/e91ea521412942d0cb4d45deefdf3db7 to your computer and use it in GitHub Desktop.
Save ugultopu/e91ea521412942d0cb4d45deefdf3db7 to your computer and use it in GitHub Desktop.

Summary

The following type of callbacks:

(err, ...args) => {
  if (err) return callback(err);
  // Rest of the callback code (that is, the "happy path").
}

are customary because the alternative is either:

(err, ...args) => {
  if (err) callback(err);
  else {
    // Rest of the callback code (that is, the "happy path").
  }
}

which causes the "happy path" to be "tucked away" in an else block, causing it to be indented by one level or:

(err, ...args) => {
  if (err) { callback(err); return; }
  // Rest of the callback code (that is, the "happy path").
}

Now our "happy path" is not "tucked away" anywhere, which is good. However, we had to put a pair of curly braces for the body of the if statement, since now we have more than one statement in the body of if.

So the reason that if (err) return callback(err); is customary because it provides our "happy path" not to be "tucked away" in an else block, while saving us 5 keystrokes (the keystrokes for { ; } when we use if (err) { callback(err); return; }, instead of if (err) return callback(err);). It is not because the "caller of the "regular" callback is expecting something (a value) to be returned back to itself".

However, note that some examples on Node.js docs use return callback(err); even within a curly braces block after an if, which is just stupid. That is, some examples use it as:

function processData (callback) {
  fetchData(function (err, data) {
    if (err) {
      console.log("An error has occurred. Abort everything!");
      return callback(err);
    }
    data += 1;
    callback(data);
  });
}

This is completely pointless because in this case, using return callback(err); instead of callback(err); return; is saving only one keystroke. We can even say that it is not saving any keystrokes at all if we use it as callback(err); return.

Details

As I have explained before, returning anything from a callback, including returning the result of a "super" callback invocation, does not make any sense. The reasons is, a callback, by definition, is a function that is called by something, when that thing is done with its operations and now wants to pass program's control to something else. That is, if that something has called a callback, that means it is done with its operations and hence, now it wants to pass the program control something else.

Consider the following function. It is a function which accepts a directory path and checks if the directory contains all files specified in an array. Note that it must accept a callback (that is, it must be an asynchronous function) because it uses an asynchronous function in it (it uses fs.readdir). That is, in order to be able to produce a result, the function must wait for the fs.readdir function to return (produce) a result. Since fs.readdir is asynchronous, it produces a result asynchronously and accepts a callback to be run when the results are ready. Hence, our function must wait for that callback to be run before being able to produce any result and hence, our function does its calculations in the callback that has been passed to fs.readdir. Because of that, anything that uses our function must pass a callback to it, because our function does not immediately return a value because it has to (asynchronously) wait for the fs.readdir to return a value. Hence, anything that depends on our function must be passed as a callback (that is, as the last argument, which is the parameter named callback) to our function. This callback argument is the "super" callback in this example. Why do we use the term "super" callback instead of simply referring to it as "callback"? Well, because there is already another callback in the function below. That "another" callback is the function that we pass to fs.readdir as the callback. We pass an arrow function to fs.readdir as the callback. Note that we could have used a regular, pre-ES6 function as well. Instead of a regular, pre-ES6 function, we used an arrow function just because an arrow function is less verbose than creating a pre-ES6 function expression. Since the function that we pass to fs.readdir as the callback would be the "regular" callback, we refer to the callback argument as the "super" callback.

Note that in asynchronous functions that use callbacks, the function body is always in the body (in the "happy path") of the callback function that is passed to the "helper" asynchronous functions that they use. For example, the operation that we perform for checking if the path argument is the correct directory is filesToCheck.every(file => foundFiles.includes(file)). That's all that we do for determining it. However, instead of being directly in the body of our function, that line is "tucked away" in the body of the callback function that we pass to fs.readdir. The reason is because that operation must wait for the result of the fs.readdir. And since fs.readdir is an asynchronous operation, its result is not immediately available, its result becomes available asynchronously, and when it becomes available, the callback function that we passed to fs.readdir is called synchronously. Only then, we can access the results of fs.readdir and we can do it (only) in the callback function that we passed to fs.readdir. For this reason, the body, the "meat" of our function is (must be) "tucked away" in the body of the callback function that we pass to fs.readdir.

function isCorrectDirectory(path, callback) {
  const filesToCheck = [
    'a-file-that-we-want.txt',
    'another-file-that-we-want.json',
  ];

  fs.readdir(
    path,
    (err, foundFiles) => {
      if (err) callback(err);
      else {
        callback(
          null,
          filesToCheck.every(file => foundFiles.includes(file)),
        );
      }
    }
  );
}

In this example, the "happy path", the "meat" of the callback, the main part that we are interested in in the else block of the callback that we pass to fs.readdir. This is not bad but it would have been better for readability if it was more obvious. For example, being in the else block causes our "happy path" to be indented by one level (because it is in a block).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment