Errors inside functions
Errors inside a function are handled with try...catch, and in asynchronous code with the same try...catch around an await or with .catch() on a promise. The goal of handling is not to hide the failure but to make the function behave predictably: either return a safe value or throw a clear error upwards.
Theory
TL;DR
try...catchprotects synchronous code: everything intryruns "under protection", and on an error control moves tocatch.- In
asyncfunctions thetry...catchgoes inside, around theawait. - Without
await, promise errors are caught by.catch()in the chain. - An error can not only be caught but also rethrown upwards with
throw new Error(...). - Use
try...catchsurgically, where you genuinely expect a failure. - Do not silence errors without logging, and return a predictable value on failure.
Quick example
function divide(a, b) {
try {
if (b === 0) {
throw new Error('Division by zero is not possible');
}
return a / b;
} catch (error) {
console.error('Error:', error.message);
return null; // safe fallback value
}
}
console.log(divide(10, 0)); // Error: Division by zero is not possible -> nullWhat happens here:
- everything inside
tryruns "under protection"; - if an error occurs, execution moves into
catch; - after that the program does not crash, and the function returns a predictable
null.
Synchronous error handling
try...catch is used when an error may happen at the moment the function runs: a bad argument, a failed string parse, reading a property of null. The catch block receives an error object that has at least name, message and stack.
It is important to understand the limits: try...catch only catches what is thrown synchronously inside the block. An error from a callback that runs later (in setTimeout, for example) will not reach that catch, because by the time it is thrown the block has long finished.
Asynchronous errors with async/await
For async functions the try...catch goes inside the asynchronous block, around the await. A rejected promise then turns into an ordinary exception that is caught right there:
async function fetchUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error('User not found');
return await res.json();
} catch (error) {
console.error('Request failed:', error.message);
return null;
}
}Note the res.ok check: fetch only rejects on a network failure, while a response with status 404 or 500 counts as perfectly successful for it. So an HTTP error has to be thrown by hand.
Errors in promises
If you do not use await, errors are caught with .catch() at the end of the chain. A single .catch() covers every preceding .then():
fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error('Error:', err));A chain without .catch() produces an unhandled rejection, which in Node.js can terminate the process.
Creating and rethrowing errors by hand
Sometimes an error should not only be caught but also passed upwards with extra context. In that case catch throws a new error:
function parseJSON(str) {
try {
return JSON.parse(str);
} catch (error) {
throw new Error('Invalid JSON: ' + error.message);
}
}
try {
parseJSON('{invalid}');
} catch (e) {
console.error('Caught at the top level:', e.message);
}This turns a low level parsing error into a readable message, while the decision about what to do next belongs to the code that called the function.
Good practices
- Use
try...catchsurgically, where you genuinely expect an error, not around the whole function body. - Do not silence errors without logging: an empty
catchis the fastest way to lose a day of debugging. - Return a predictable value on failure:
null,{},false, and document it. - For server side functions log through a
logger, Sentry or at leastconsole.error. - For pure functions prefer throwing over logging: let the calling code decide.
Common mistakes
- An empty
catch.catch (e) {}makes the failure invisible, and a program that "just does nothing" is impossible to diagnose. - Wrapping an asynchronous callback in
try...catch. PuttingsetTimeoutor an old style callback insidetrycatches nothing: the error happens after the block has exited. - Forgetting
awaitbefore a promise insidetry. Withoutawaitthe function returns a promise and the rejection flies past thecatch. - Assuming
fetchthrows on a 404. It only rejects on a network failure, so the status has to be checked manually. - Throwing a string instead of an
Error.throw 'oops'deprives you of a stack trace and breaks any code that expectserror.message. - Catching an error where you can do nothing about it. If a function cannot recover, it is more honest to rethrow than to return a silent
null.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.