Return a promise for cleaner API

Instead of returning an error code or value, return a Promise.

I advise to return a promise if the API might become asychronous one day. In this blog post I will advise to return a promise in case the api function returns an error. Returning the promise will allow the caller to handle the error in much cleaner way, even allowing the code up the calling stack to handle it.

Imagine you are designing a function that reads a file and returns its content. How will it handle the "file does not exist" problem? It can throw an error, forcing its users to surround the call with a try - catch block.

1
2
3
4
5
6
7
8
9
10
11
12
13
function readFile(filename) {
var fs = require('fs');
if (!fs.existsSync(filename)) {
throw new Error('File ' + filename + ' does not exist');
}
return fs.readSync(filename);
}
// user code
try {
var contents = readFile('foo.txt');
} catch (err) {
// hmm, what should we do?
}

My main problem with try - catch block is that it is NOT optimized by the JavaScript engine, leaving the performance less than stellar. It is also boilerplate solution, adding a lot of source code.

We could return some error value, for example undefined or null

1
2
3
4
5
6
7
8
9
10
11
12
function readFile(filename) {
var fs = require('fs');
if (!fs.existsSync(filename)) {
throw new Error('File ' + filename + ' does not exist');
}
return fs.readSync(filename);
}
// user code
var contents = readFile('foo.txt');
if (contents === null) {
// something went wrong
}

I do not like this solution either. Whatever we pick: undefined, null or 0xBAD could be a valid contents, forcing us to pick more obscure values, or even pushing the API to check the value for us. For example, WIN32 apis often contain a macro for checking if the handle / value returned by an API call is valid, see HRESULT and SUCCEEDED macro.aspx), while the data contents is passed through a parameter.

pointer contents;
HRESULT result = Win32ReadFile(filename, contents);
if (!SUCCEEDED(result)) {
    // ok, bad, contents is invalid
}

In JavaScript, a lot of APIs return -1 to signify an error.

1
2
3
4
var contents = readFile('foo.js');
if (contenst === -1) {
// bad!
}

We could use a shortcut to compare a value with -1 (for example String.prototype.indexOf), but I would strongly advise against it for clarity sake. For example, this unit test shows it is working, but in very unclear fashion.

1
2
3
4
5
6
~-1; // 0
// any other integer value with ~ before it will NOT be 0
~-2; // not 0
if (!~'foo'.indexOf('oo')) {
// found it!
}

Promise api

Instead, I prefer returning a promise. The best thing about the promises, is that you can upgrade the function to be asynchronous later, and it has separate callbacks for success and failure. We can resolve with an Error instance in our API.

1
2
3
4
5
6
7
8
9
function readFile(filename) {
var fs = require('fs');
return new Promise(function (resolve, reject) {
if (!fs.existsSync(filename)) {
return reject(new Error('File ' + filename + ' does not exist'));
}
return resolve(fs.readSync(filename));
});
}

Notice that we:

  • return immediately after rejecting, making sure we stop the readFile function.
  • Reject with a valid Error instance, and not just a string or an integer code. This ensures that the error has a valid stack.

The caller than can easily pipeline actions for success or failure

1
2
3
4
5
6
7
8
readFile('foo.txt')
.then(function hasFile(contents) {
// all good
})
.catch(function (err) {
// handle error: report, retry, etc.
})
.done();

Notice that in this case, the .catch callback will be executed if the readFile fails, and if there is an error inside the success callback hasFile. Just remember: every step in the promise chain executes via the event loop queue, so interesting side effects are possible.