Catch Errors in JavaScript Promise Chains

Understanding how to catch errors in promise chains or even when to use error catching with promises can be confusing. Furthermore, there are a number of ways to catch and throw errors with libraries like Q. However, if you implement error handling appropriately it can be helpful in debugging and in providing informative error messages to users.

A hypothetical example:

function fetchData() {
    return Q([1, 2, 3]);
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('handling ' + data);
            return data.reduce(function (memo, val) { return memo + val; });
        }, function (error) {
            console.error('handle error: ' + error.stack);
            throw error;
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        })
        .fin(function () {
            console.log('finish');
        });
}
 
useData();
// start
// handling 1,2,3
// using 6
// finish

JS Fiddle

In this example fetchData simply returns a fulfilled promise, but pretend it’s a function that asynchronously fetches data. Upon successfully fetching the data, the function would fulfill the promise with the data. Alternatively, fetchData could resolve with a rejected promise. handleData and useData simply represent consuming functions since nesting functions that return promises can be a common way to breakdown functionality. The calls to console.log and console.error represent whatever “work” is done after the promise is fulfilled or rejected which may include error logging and/or the error messages displayed to users in the event of unexpected or expected errors (e.g. invalid user input). The error handling in this example is purely demonstrative and doesn’t represent best practice.

Now let’s introduce errors into the example and show possible ways to handle them.

Instead of fulfilling the original promise, I’m going to modify fetchData to return a rejected promise with an error.

function fetchData() {
    return Q.reject(Error('401 Unauthorized'));
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('handling ' + data);
            return data.reduce(function (memo, val) { return memo + val; });
        }, function (error) {
            console.error('handle error: ' + error.stack);
            throw error;
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        })
        .fin(function () {
            console.log('finish');
        });
}
 
useData();
// start
// handle error
// use error
// finish

JS Fiddle

In the Q API reference, the first and second parameters of promise.then are known as onFulfilled and onRejected respectively. If you run the example above with the modified fetchData function you’ll notice that the onRejected function is called in each then method within handleData and useData.

It is important to notice that the onRejected function in handleData does not simply return the error, but throws the error. To understand what’s really going on here it’s important to understand that when then is executed it creates a new promise. To explain it using code:

promise2 = promise1.then(onFulfilled, onRejected);

If you compare this snippet to the example above, promise1 represents the promise returned by fetchData. promise2 represents the promise returned by then in handleData and is the same promise on which then is executed in useData. A common point of confusion and incorrect assumption is that handleData is returning the promise returned by fetchData, which is NOT what happens.

promise2 is either fulfilled or rejected based on what happens in onFulfilled and onRejected. If you want an error to be caught by a subsequent onRejected function then you must throw the error and not return it. Throwing the error essentially rejects the promise created by then (promise2). In other words, when then is executed it creates a new promise, which is fulfilled if no error is thrown or rejected if you throw an error in onFulfilled or onRejected.

In the event you don’t want to throw the error, you could handle it and return a rejected promise so that any subsequent onRejected functions are called. Additionally, you could handle the error and return nothing so that the promise resolves and any subsequent onFulfilled functions are called.

The following example demonstrates what happens when you return an error:

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('handling ' + data);
            return data.reduce(function (memo, val) { return memo + val; });
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return error;
        });
}
 
useData(); 
// start
// handle error
// using Error: 401 Unauthorized
// finish

JS Fiddle

The onFulfilled function is called in useData because the error is returned. The intention of the code is not to use the error, thus it should throw it or return something useable instead.

What if we needed to fetch other data based on the original data we received? Our data fetching and handling functions might look something like this:

function fetchData() {
    return Q([1, 2, 3]);
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchOtherData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .fin(function () {
            console.log('finish');
        });
}

useData(); 
// start
// fetching other data with 1,2,3
// using 2,4,6
// finish

JS Fiddle

With this example, don’t focus on what is happening to the data but simply the idea that something else asynchronous is happening that requires a promise. It just so happens I decided to “fetch more data” based on the initial dataset because I’ve seen this happen in real applications. Also, I moved some of the data handling to fetchOtherData.

Now I’m going to start making some changes. Let’s say that I misspell the fetchOtherData function and call it fetchMoreData, which is an undefined function. What happens?

function fetchData() {
    return Q([1, 2, 3]);
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .fin(function () {
            console.log('finish');
        });
}

useData();
// start
// fetching other data with 1,2,3
// finish

JS Fiddle

You may have expected the error handler onRejected in handleData to catch the exception that was thrown from calling the undefined function fetchMoreData. However, that is not the case. The onRejected function in handleData only handles errors or rejected promises returned from the preceding promise, in this case, the promise returned by fetchData. An onRejected function will NOT handle a rejected promise or error thrown in a neighboring onFulfilled function, (i.e. the onFulfilled function that is passed as an argument to the same then that onRejected is passed to).

You also may be surprised to notice that the console reports no errors whatsoever. Since we didn’t provide adequate error handling in this scenario, the exception was “swallowed” in our promise chain. As you can imagine, this would make debugging pretty tricky and frustrating. There are a number of different things we could do to correct this.

You could add a call to fail (also known as catch) in your promise chain. fail is simply syntactic sugar for then(null, onRejected).

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        })
        .fail(function (error) {
            console.error(error.stack);
        });
}

useData(); 
// start
// fetching other data with 1,2,3 
// ReferenceError: fetchMoreData is not defined
// finish

JS Fiddle
The onRejected function passed to fail receives the return value of the rejected promise returned by then, which in this case is a ReferenceError exception.

Additionally, you could remove the onRejected passed to then fail so as not to have two onRejected functions that essentially handle errors the same way. However, depending on your situation you may want separate onRejected functions to handle the rejections differently.

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        })
        .fail(function (error) {
            console.error(error.stack);
        });
}

useData(); 
// start
// fetching other data with 1,2,3 
// ReferenceError: fetchMoreData is not defined
// finish

JS Fiddle

This newly added call to fail will handle rejections from the preceding then and any rejections from fetchData.

function fetchData() {
    return Q.reject(Error('BAZINGA!'));
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        })
        .fail(function (error) {
            console.error(error.stack);
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .fin(function () {
            console.log('finish');
        });
}

useData();
// start
// ReferenceError: BAZINGA!
// finish

JS Fiddle

As a final example, it’s usually a good idea to add done to the end of promise chains because it won’t swallow nasty errors the way then does.

function fetchData() {
    return Q([1,2,3]);
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        });
}
 
function useData() {
    console.log('start');
    handleData()
        .done(function () {
            console.log('finish');
        });
}

useData();
// start
// fetching other data with 1,2,3 
// Uncaught ReferenceError: fetchMoreData is not defined 

JS Fiddle

Notice that done “handles” the error without an onRejected error handler. In reality, it doesn’t handle the error but allows it to be raised to window.onerror.

The difference between then and done can be confusing. The Q API provides a great rule of thumb:

The Golden Rule of done vs. then usage is: either return your promise to someone else, or if the chain ends with you, call done to terminate it. Terminating with catch is not sufficient because the catch handler may itself throw an error.

This is because “exceptions thrown in then callbacks are consumed and transformed into rejections, [so] exceptions at the end of the chain are easy to accidentally, silently ignore” where as with done “the resulting rejection reason is thrown as an exception in a future turn of the event loop” (Q API). done makes sure the error is not caught and not ignored so that some kind of error is raised.

Also, I could have provided an onRejected function to done and handled the error some other way, but in this example I wanted to illustrate that done wouldn’t swallow the error like then.

As an interesting side note, if you were to store the promise that was swallowed in the original example above and inspect it, you would notice that it has been rejected. If you call fail or done on that promise, you can handle the rejection like so:

function fetchData() {
    return Q([1,2,3]);
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchMoreData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        });
}
 
function useData() {
    console.log('start');
    return handleData()
        .fin(function () {
            console.log('finish');
        });
}

var prom = useData();
Q(prom).fail(function () {
    console.log(prom.isRejected());
    console.log(prom.inspect());
});
// start
// fetching other data with 1,2,3
// finish 
// true
// Object {state: "rejected", reason: ReferenceError}

JS Fiddle

All said and done (pun intended), there is one final question I want to address: What if an error is thrown in done?

You may have noticed I already answered this when quoting the Q API, but I would like to provide an example.

function fetchData() {
    return Q([1,2,3]);
}

function fetchOtherData(params) {
    return Q(params.map(function (val) { return val * 2; }))
        .then(function (result) {
            console.log('using ' + result);
        }, function (error) {
            console.error('use error: ' + error.stack);
        });
}

function handleData() {
    return fetchData()
        .then(function (data) {
            console.log('fetching other data with ' + data);
            return fetchOtherData(data);
        }, function (error) {
            console.error('handle error: ' + error.stack);
            return Q.reject();
        });
}
 
function useData() {
    console.log('start');
    handleData()
        .done(function () {
            throw Error('oops!');
            console.log('finish');
        }, function (ex) {
            console.error(ex.stack);
        });
}

useData();
// start
// fetching other data with 1,2,3
// using 2,4,6
// Uncaught Error: oops! 

JS Fiddle

Even when an error is thrown in the onFulfilled function passed to done, the error will still be raised. Similar to then, the onRejected function I provided to done does not handle the error thrown in onFulfilled. It is simply thrown as an Uncaught Error.

Now that the promise chain has been terminated with done, if you were to continue the promise chain you would get an error because nothing is returned.

function useData() {
    console.log('start');
    return handleData()
        .done(function () {
            console.log('finish');
        }, function (ex) {
            console.error(ex.stack);
        });
}

useData().then(function () {}, function () {});
// Uncaught TypeError: Cannot read property 'then' of undefined
// fetching other data with 1,2,3
// using 2,4,6
// finish

JS Fiddle

Concurrent Promises with Q

What if you want to execute multiple promises concurrently (using Q)?

Q(Q(1), Q(2), Q(3))
    .then(function (one, two, three) { 
        console.log(one);
        console.log(two);
        console.log(three);
    }, function (ex) {
        console.error(ex.stack);
    });
// 1

You can’t simply wrap them all with a call to Q. Only the first promise will be handled as expected. It’s as if the other 2 are just ignored.

Enter Q.all. Instead of passing your promise functions as individual arguments to Q, pass them as an array to Q.all. (Careful not to forget the array!)

Q.all([Q(1), Q(2), Q(3)])
    .then(function (one, two, three) {
        console.log(one);
        console.log(two);
        console.log(three);
    }, function (ex) {
        console.error(ex.stack);
    });
// [1,2,3]

Now all three promises are handled as expected; however, their return values are passed back as an array. That’s easy enough to work with.

Q.all([Q(1), Q(2), Q(3)])
    .then(function (one, two, three) {
        console.log(one[0]);
        console.log(one[1]);
        console.log(one[2]);
    }, function (ex) {
        console.error(ex.stack);
    });
// 1
// 2
// 3

Q offers a way to get the return values back as parameters instead of an array: use spread instead of then. The return values become the parameters of the function you pass to spread, and like the array above, the order of the parameters corresponds to the order you call the respective promise functions.

Q.all([Q(1), Q(2), Q(3)])
    .spread(function (one, two, three) {
        console.log(one);
        console.log(two);
        console.log(three);
    }, function (ex) {
        console.error(ex.stack);
    });
// 1
// 2
// 3

What happens when one or more of the promise functions is rejected or throws an error?

Q.all([Q(1), Q.reject('rejected!'), Q.reject('fail!')])
    .spread(function (one, two, three) {
        console.log(one);
        console.log(two);
        console.log(three);
    }, function (reason, otherReason) {
        console.log(reason);
        console.log(otherReason);
    });
// rejected!
// undefined

The subsequent onRejected function is called with the rejection reason of the first rejected promise.

Return Fulfilled or Rejected Promise

Today I ran into a situation where one of my functions was part of a chain of promises and I had a condition where if something was true, I didn’t want to fetch some data asynchronously, but if it was false I did want to fetch the data. My initial code looked similar to this:

var i = 0;

function getDataAsync() {
    var def = Q.defer(),
        j = ++i;

    console.log('fetching ' + j);
    setTimeout(function () {
        console.log('done ' + j);
        def.resolve();
    }, 1000);

    return def.promise;
}
 
function doSomething(willGetData) {
    if (willGetData) {
        return;
    }
    return getDataAsync()
        .then(getDataAsync)
        .then(getDataAsync);
}
 
function doSomethingMore(willGetData) {
    console.log('start');
    doSomething(willGetData)
        .then(function () {
            console.log('finish');
        });
}
 
doSomethingMore(true); //Cannot read property 'then' of undefined

You’ll notice the problem lies in doSomething where I am simply returning undefined. The error occurs in doMore because then is not a property of undefined. I needed to return a fulfilled promise to keep the chain unbroken. My initial solution looked something like the code below, but it just felt awkward, like there was too much going on. Too much extra code and…yuck.

function doSomething(willGetData) {
    var def = Q.defer();

    if (willGetData) {
        def.resolve();
        return def.promise;
    }
    
    getDataAsync()
        .then(getDataAsync)
        .then(getDataAsync)
        .then(def.resolve, def.reject);

    return def.promise;
}

So I read through the Q documentation and found a more elegant solution.

function doSomething(willGetData) {
    if (willGetData) {
        return Q();
    }
    return getDataAsync()
        .then(getDataAsync)
        .then(getDataAsync);
}

Calling Q(value) where value isn’t a promise returns a fulfilled promise with value. Conversely, if you needed to return a rejected promise with a value you could use Q.reject(value).

As a fun side note, I also toyed with this hack, which I don’t recommend:

function getThenable() {
    return {
        then: function (func) {
            func();
            return getThenable();
        }
    };
}

It could be used in place of Q() because it returns a ‘thenable’ object.

JSFiddle anyone?

Return Data in JavaScript Promises

As I was coding promises in a new module the other day, I wondered if I could return data in my anonymous function passed to then. Previously, it seems like I almost always returned data via resolve (i.e. def.resolve(data);). I probably just overcomplicated things and had too many promises involved. At any rate, I was almost sure what I wanted to do would work, but to test it in isolation I wrote up the functions below.

function createPromise(data) {
    return Q(data);
}
 
function doSomething() {
    var otherData = [1, 2, 3];
    return createPromise(['a', 'b', 'c'])
        .then(function (data) {
            console.log(data); //['a', 'b', 'c']
            return otherData;
        });
}
 
function doSomethingMore() {
    console.log('start');
    return doSomething()
        .then(function (otherData) {
            console.log(otherData); //[1, 2, 3]
            console.log('finish');
        });
}
 
doSomethingMore();

Some explanation and observations:

First. If you were unaware, calling Q(value) where value is not a promise returns a fulfilled promise with value.

Second. The createPromise function isn’t really necessary. I could just as easily substitute createPromise() in doSomething with Q(['a', 'b', 'c']). However, functions are often a bit more complex and I wanted the example to illustrate that this could very well involve 3 separate functions.

Third. then returns a promise such that promise2 = promise1.then(onFulfilled, onRejected, onProgress). promise2 is not fulfilled until the appropriate onFulfilled or onRejected function has finished running.

Fourth. You could just as easily nest a function that returns promise3 and promise2 would not resolve until promise3 had been fulfilled or rejected. For example:

function doSomething() {
    var otherData = [1, 2, 3];
    return createPromise(['a', 'b', 'c'])
        .then(function (data) {
            console.log(data);
            return createPromise(otherData);
        });
}

Most of these details (if not all) are explained in the Q API documentation. Take a gander.

JSFiddle if you like.

Overpromising with Multiple Promises in JavaScript

When dealing with multiple promises in JavaScript, it’s easy to “over-promise” or create too many promises. For example, let’s say you have a function that needs to return a promise because it also calls a function that returns a promise:

function doSomething() {
    var def = Q.defer();
    createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
            def.resolve();
        });
    return def.promise;
}

Instead, this function could be tightened up to look like this:

function doSomething() {
    return createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
        });
}

You don’t need to create and return a new promise. Just return the promise from the function that creates a promise. Sometimes, you can’t get away with this, but often you can.

This example also demonstrates how then returns a promise and resolves that promise only after the onFulfilled (or onRejected) function is executed. I didn’t use any onRejected functions to keep things simple.

Now, to give it more context with a contrived working example:

var i = 0;

// some function that does both synchronous and asynchronous stuff
// does not return a promise nor does it need to
function runTimeout(defObj, time) {
    time = time || 2000;
    console.log('timeout start.');
    setTimeout(function () {
        console.log('timeout finish.');
        if (defObj) {
            defObj.resolve();
        }
    }, time);
}

// some function that creates and returns a promise
function createPromise() {
    var def = Q.defer();
    console.log('createPromise ' + ++i);
    runTimeout(def);
    return def.promise;
}
 
// some function that executes a function that returns a promise
// also needs to do stuff after the promise is resolved
function doSomething() {
    return createPromise()
        .then(function onFulfilled() {
            console.log('first then');
            runTimeout();
        })
        .then(function onFulfilled() {
            console.log('second then');
            runTimeout();
        });
}

// another function that executes a function that returns a promise
// lots of nested promises at this point
function doMore() {
    console.log('start');
    doSomething()
        .then(createPromise)
        .then(function () {
            console.log('finish');
        });
}
 
doMore();

Test it in this JSFiddle.

You could argue that the functions/promises in this (naive) example could be ‘flattened,’ but when you are developing modules with dependencies on other modules, you can’t always flatten your functions into one chain of promises in one function. Furthermore, you wouldn’t want to because you’ve designed your modules to encapsulate specific logic (i.e. single responsibility principle) and that’s good.

If you’re really digging into JavaScript and promises then you ought to read the Promises/A+ specification for exact details on how then works. It’s a bit scary when you first look at it, but spec is outlined logically so it’s pretty easy to understand if you take the time.

JavaScript Function Declarations vs Function Expressions

I recently revisited the differences between Function Declarations (FD) and Function Expressions (FE). I wanted to see if there were any compelling reasons to use one over the other, or if both were considered acceptable. I’ve taken most of my thoughts from a blog post by Angus Croll. It’s worth a read if this is a new topic for you.

In general, FDs and FEs are interchangeable because they both create a function. However, there can be some interesting gotchas (as always with JavaScript).

Even though all browsers handle Function Declarations within non-function blocks (e.g. if), they are technically prohibited. The caveat is that each browser handles them in its own way.

function a() {
    if (false) {
        function b() { return 'Will it return?'; }
    }
    return b;
}
console.log(a()); // result depends on browser

A Function Declaration is scoped to itself and its parent. As a result, you can call it recursively and in the parent (it would be mostly useless otherwise). For example:

function minusOne(positiveNumber) {
    console.log(positiveNumber--);
    if (positiveNumber > -1) {
        return minusOne(positiveNumber);
    }
}
minusOne(2);   // 2 1 0

A Function Expression works just as well:

var minusOne = function (positiveNumber) {
    console.log(positiveNumber--);
    if (positiveNumber > -1) {
        return minusOne(positiveNumber);
    }
};
minusOne(2);   // 2 1 0

You could make the FE above a Named Function Expression (NFE) by naming the anonymous function subtractOne and it would still work. Furthermore, you could replace the recursive call to minusOne with subtractOne and it would work (assuming you named the anonymous function subtractOne). The following, however, would NOT work:

var minusOne = function subtractOne(positiveNumber) {
    console.log(positiveNumber--);
    if (positiveNumber > -1) {
        return minusOne(positiveNumber);
    }
};
subtractOne(2);   // subtractOne is not defined

You cannot call the named function outside of its assignment.

Here’s another function I wrote just for fun:

function moveToZero(number) {
    var absNumber = Math.abs(number);
    console.log(number);
    if (absNumber-- !== 0) {
        return moveToZero(number > -1 ? absNumber : ++number);
    }
}
moveToZero(2);  // 2 1 0
moveToZero(-2); // -2 -1 0

Function Expressions are more versatile. They are the essence of functional programming. You can create Function Expressions using anonymous functions. You can assign functions to objects as properties or, more specifically, assign them to prototypes. Immediatley Invoked Function Expressions (IIFEs) are considered Function Expressions. As Croll points out, currying and composing use FEs.

One of the caveats to Function Expressions is that functions are often created using anonymous functions, which can make debugging a pain. As a work around you can use Named Function Expressions, as seen in the examples above. However, NFEs are not supported in non-modern browsers (IE8 and below). Always something to keep in mind.

Many developers steer away from Function Declarations because they can be confusing. There aren’t many, if any, times you cannot replace a Function Declaration with a Function Expression. FEs are often favored for consistency and versatility.

Difference Between bin and sbin

Ever been curious about the difference between bin and sbin? The ‘s’ in sbin means ‘system’. Therefore, system binaries reside in sbin directories.

As you may have noticed, there are a number of different bin directories in Linux. The best reference I’ve found for an understanding of various Linux folders is man hier. It provides a brief explanation of the Filesystem Hierarchy Standard (FHS) in Linux. I’ve included a summary of the various bin and sbin definitions below:

/bin
    This directory contains executable programs which are needed
    in single user mode and to bring the system up or repair it.

/sbin
    Like /bin, this directory holds commands needed to boot the 
    system, but which are usually not executed by normal users.

/usr/bin
    This is the primary directory for executable programs. Most
    programs executed by normal users which are not needed for 
    booting or for repairing the system and which are not
    installed locally should be placed in this directory.

/usr/local
    This is where programs which are local to the site typically
    go.

/usr/local/bin
    Binaries for programs local to the site.

/usr/local/sbin
    Locally installed programs for system administration.

If you want to create your own scripts and make them available to all users, you’re pretty safe adding them to /usr/local/bin. If you want to run scripts using cron or crontab, simply use the full path to the command (i.e. /home/user/command).

What I do is add my scripts to my local bin (~/bin) and then I create a symbolic link in /usr/local/bin to the commands I want to make public. As a result, I can manage all my scripts from the same directory but still make some of them publicly available since /usr/local/bin is added to $PATH.