Let's Clean Up: Ugly Try-Catches!
We've all been there. We have all usedawait
onasync
methods, forgetting to wrap them intry-catch
just to be told off about anUnhandled Error
😱
But it's not just theseaysnc
methods that might throw. Perhaps we use a third-party library that throws, or we design our codebase in such a way that we throw errors intentionally to bubble error handling up a few layers.
So we proceed by wrapping our calls to the throwable methods in ourtry-catch
blocks.
Perfect! 😍
🤔 Or is it?
Working in a code base where you can expect methods tothrow
can lead to situations where your logic is wrapped intry-catch
blocks. It also leads to other code design problems.
Take a look at the examples below:
try{constmyVar=myThrowableMethod();}catch(err){console.log(err);}// Wait, there's an error here:console.log(myVar);// myVar doesn't exist outside of the try scope.
As we can see above, themyVar
variable doesn't exist outside of thetry
block. But we need that value to continue our logic!!
So now we need to do something a little different.
How about:
try{constmyVar=myThrowableMethod();constnewVar=manipulateMyVar(myVar);constresponse=sendApiRequest(newVar);returnrespponse;}catch(err){console.log(err);}
🤮!
No. Let's not do this.
Now all our logic wrapped inside thetry
block. That's ugly.
Also, if any of the subsequent method calls throw, are we sure we want to handle them the same way!? Possibly, but most likely not!
Ok, let's try something else...
letmyVar;try{myVar=myThrowableMethod();returnrespponse;}catch(err){console.log(err);}constnewVar=manipulateMyVar(myVar);constresponse=sendApiRequest(newVar);
This is a little better, but still not perfect. It's still ugly asmyVar
has to be declared then initialised almost immediately after just in a different scope. It also presents an issue whenmyThrowableMethod
does throw, as execution will continue and try to usemyVar
!
🐛 Alert!
I could keep going, giving more situations where thesetry-catch
blocks can present code design, readability, and maintainability problems.
Instead, I'll present you with a solution!
The Solution 🚀
I wrote a small library to tackle this issue head on :
Let's welcomeno-try to the scene. 🎉
What isno-try
? 😱
no-try
is a tiny library that takes thetry-catch
out of your code, improves the readability and maintainability of your code whilst helping to improve code design.
It exposes two functions.noTry
andnoTryAsync
with the latter resolving and returning the result of Promises.
Don't believe me? Let's look at it in more detail.
To install it, simply runnpm i --save no-try
Then add it to your file:
In TypeScript;
import{noTry}from"no-try";
In JS:
constnoTry=require("no-try").noTry;
Now, let's refactor our example above to useno-try
.
const{result,error}=noTry(()=>myThrowableMethod());if(error){// Handle errorreturn;}constnewVar=manipulateMyVar(result);constresponse=sendApiRequest(newVar);
🎉🎉🎉
Isn't that cleaner!?
If you have a standard error handling function, you can supply that tonoTry
and it will invoke it for you if an error occurs!
functionmyCustomErrHandler(error){// Do something to handle error}const{result,error}=noTry(()=>myThrowableMethod(),myCustomErrHandler);if(error){return;}constnewVar=manipulateMyVar(result);constresponse=sendApiRequest(newVar);
And that's it!
We've removed thetry-catch
blocks from our code, preventing issues relating to block-scoped variables, whilst also allowing our code to be much more readable without sacrificing the flexibility of handling the error how we want.
You can read more onno-try
over onGitHub.
Now go clean your code!
If you have any questions, feel free to ask below or reach out to me on Twitter:@FerryColum.
Top comments(13)

Isn't that cleaner!?
Imho, no. That's adding a useless dependency to a problem that's easily solved by addingreturn
statement in the catch block. Could probably benefit from using more functional programming to refactor that bit but that depends purely on what other stuff is going on before this block.

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
Agreed. You can add the return to the catch to break execution in the method, but you're still left with what I feel is less readable code. I guess it comes down to a matter of preference! :)

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
Theno-try
lib should still provide you with all the control you need :)
It just removes the need to write the try catch blocks manually. It still uses them under the hood.
- LocationUk
- WorkCyber Security team lead at ZF
- Joined
Good code should read like a document, really good code can be understood by a non coder.
There are sometimes benefits from abstracting complex functionality, but it is a balance of readability, functionality and performance.
Code is first and foremost a document describing the behaviour of your action.
Looking at it from a different perspective, if a Dev was to come along and they had never seen this code or your no-try mechanism.
They have to read your code, then they have to go and learn your no-try and maybe they'll understand it, maybe they won't.
However there is a high probability they've seen a try-catch, so they don't need to learn this functionality and so immediately understand what you are trying to achieve!
This is an over abstraction which makes you feel better as a particular function has been reduced, but as a piece of code actually over complicates the readability of the code as it deviates from normal coding practice.
Try catch is easily explained to a non-coder, your function less so, especially as the name doesn't reflect the functionality of it's operation.
This is why function naming is critical in abstraction.

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
I understand what you're saying, and it makes sense. There's a cost to learning what the method does, but the same can be said for any dependency you add to your codebase.
Take for example someone adding lodash, are using both_.clone
and_.cloneDeep
but someone else comes along to read it. If they don't know about lodash, or the difference between those functions, they will have to go and look them up to.
Perhaps simply calling the functiontryCatch
might have explained more what the function is doing, BUT, for any dev that wants it to be called this, then they can alias the import.
import { noTry as tryCatch } from 'no-try';
Or
const tryCatch = require('no-try').noTry;

Ugly isn't really a professional adjective to use.
A chunk of code can be simple or complex, fast or slow, stateful or stateless, among others measurable metrics.
Saying a code is "clean" or "ugly" is really subjective, it's an opinion. For example, a try/catch isn't intrinsically ulgy nor bad by itself.
Every instruction in a codebase is a technical choice made in a certain context that must be understood. A hackathon blockchain explorer will have a different codebase than a battle-tested and historical ERP.
It's pretty reductive for our field to use such adjectives.
I don't wanna be rude, but the JavaScript ecosystem doesn't need a new (26 lines of code) package while receiving a "go clean your code" with it.
I know there is a lot of new developers on this platform, and this is awesome! But this is not the kind of message we need to send them.

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
Agree: it is subjective.
Disagree: JS ecosystem doesn't need a new package (whatever the reasoning behind it is).
Without developers trying to think of new ways to make their own and others lives maybe just that little bit easier, then we would never innovate. We wouldn't get new technologies.
If a dev, at any level, has an idea for how to make their life easier, we should be at least encouraging them to investigate it, not telling them to sit on it just because it may not be needed, (which is also subjective).
Is native XHR API bad or ugly? Subjective, but we still have Fetch, Axios, Angular Http etc.
So yes, it's subjective. If you don't mind try catches, still use them, if you don't like how they look, or have been burned with bugs caused or relating to their usage, then there is an alternative.

There is already another solution that was created years ago, he took approach to handle errors same way as in GO
Anyway good job mate !

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
Thanks!
Awesome link, there is something similar to the above called await-of that also handles promises, I wanted a method that also worked for standard methods that throw errors.

- Locationromania
- Educationbachelor
- Workwebdev at not important
- Joined
Question, shouldn't try catch be used at the beginning of writing your code and after that you clean it up, because you know now that the code is stable?

- LocationNorthern Ireland
- WorkSenior Software Engineer
- Joined
I'm not sure I fully understand what you are saying.
It sounds like you're saying you wrap the full app startup in a try catch and then come along later and remove it?
Try catches should be wrapped around any method you expect can throw an error at the layer you wish to handle it.
Perhaps you have three layers:
UI
Business
Data Layer
Your data layer may throw and error but you want to catch it at the UI level so you can show an error to the user.
Or perhaps, your business layer throws an error, but you want to catch it immediately so you can call a fallback method etc.
For further actions, you may consider blocking this person and/orreporting abuse