Their is already a question on this topic
Node.js Best Practice Exception Handling
which is old and answers are very much outdated, domains have even deprecated since then. 
Now in a post Async/Await Node.js scenario shouldn't we consider sync and async cases similarly and throw exceptions in sync functions and rejecting promises in async functions instead of returning an Error instance in the former case.
let divideSync = function(x,y) {
    // if error condition?
    if ( y === 0 ) {
        // "throw" the error 
        throw new Error("Can't divide by zero exception")
    }
    else {
        // no error occured, continue on
        return x/y
    }
}
Simulating async divide operation
let divideAsync = function(x, y) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      // if error condition?
      if (y === 0) {
        // "throw" the error safely by rejecting the promise
        reject (new Error("Can't divide by zero exception"));
      } else {
        // no error occured, continue on
        resolve(x / y)
      }
    }, 1000);
  })
};
So sync and async exceptions can be handled in a uniform manner
let main = async function () {
    try {
        //const resultSync = divideSync(4,0);
        const resultAsync = await divideAsync(4,0);
    }
    catch(ex) {
        console.log(ex.message);
    }
}
 
     
    