Better errors handling for ES/Typescript classes
Writing a custom catch decorator.
In general, errors handling in JavaScript is not a very complicated topic, but it is very useful. When different errors occur during your application’s life, break it and leave the user bewildered by what is happening. 😵
If you want to protect a part of your code from possible errors, you can just run and test a block of code for errors and, if they occur, handle them and notify user with some message.
All of this can be done using try/catch
blocks or .catch
method of Promise
objects. So far so good. In this post, we’ll dive in to bringing theory to practice with error handling in JavaScript applications. Let’s get started.
This post assumes basic-decent background in JavaScript.
All next examples will be shown in case of Vue class components, as typical errors handling situations. But it can be used with any ES/Typescript classes!
Moving on. ⏬
Tip: Use Bit to encapsulate modules/components with all their dependencies and setup. Share them in Bit’s cloud and collaborate with your team.
You should “try” 🧗
Most tutorials regarding error handling in Vue components (usually errors are displayed as “toast” messages to the user) will show an implementation that looks something like this (login example):
The above implementation is correct and looks great.
- We make an API request and wait for a response (token)
- If there are no errors → handle response
- If an error is catched in
loginUser
method → go to catch block and handle error
This looks easy 😎.
But, what happens when your application is larger that a small tutorial and it contains a lot of API requests and other stuff which can throw errors?
Well, it will look like this:
As you can see, alot of try/catch
blocks make for some pretty code ugly, imperative and repetitive code (the same errors handling using toast notifications).
It’s better to make your code more declarative, describing what the program must accomplish, rather than just describe how to accomplish it.
So, we should find a way to hide this try/catch
logic and find a more elegant solution to handle errors in our Vue.js app. Let’s give it a go.
Vue instruments ⛏
If you open Vue’s documentation, you can find a global options property named errorHandler
, to which a handler for uncaught errors can be assigned. This should be the perfect option for us, so let’s try:
Great! 😃 We removed the ugly try/catch
blocks and moved handling to theglobal errorHandler
method. Let’s start the app:
Hmmm… Error from mounted
hook catched, but the one from created
is not. The reason for this (Vue’s documentation doesn’t tell anything about it) is that the errorHandler
doesn’t catch errors in asynchronous methods (and native event handlers). Internally, Vue runs our created
hook as synchronous, not waiting for completion of an asynchronous operation, when an error can actually be thrown.
Moving on. ⏬
Let’s catch 🎣
During research, I stumbled upon some Java code and saw interesting error handling, natively supported in Java:
Here throws IOException
tells the compiler that some exception can be thrown in this method and should be handled. It would be great to implement something similar in our case.
However, JavaScript doesn’t have a native mechanism which allows us to implement such a functionality. True… but it does have decorators! 😈
A Decorator is a programming pattern, and below you can see one of it’s implementations:
For those who hear about it for the first time (read more), function log
in the example above is a decorator. It takes getData
function as an argument and extends the behavior of the latter function with logging functionality.
Decorator is a structural design pattern that lets you attach new behaviors to objects by placing them inside wrapper objects that contain these behaviors.
But JavaScript has it’s own decorators implementation — a function that is invoked with a prefixed @
symbol, and immediately followed by a class
, parameter, method or property:
Here, @Catch()
annotation is our decorator. Let’ implement it. Referencing the ES2016 specification, a decorator is an expression which returns function and can take as arguments:
- target — class, where this decorator is actually used;
- name — method/property name to which this decorator is applied;
- property descriptor—specific object that describes the property’s attributes.
Base decorator:
First of all, we save the descriptor value to originalMethod
. Then rewrite the original value with our version of target function, where we run original method with arguments from wrapper function.
We use .apply
to link the method with an original context(this
). Calling original method wrapped with try/catch
block, and, for now, just show messages on any error. After that, return descriptor back.
Note:
await
method works for both sync and async functions.
Using our custom decorator in a component:
Let’s start the app:
It works! 🎉 Now synchronous (from mounted
hook) and asynchronous (from created
hook) errors are properly handled and all this works with only one annotation.
You can also use it for regular Promise
without async/await
, it will work too:
For now, our decorator has a hardcoded error handler, and this solution is not very flexible. So, let’s create some store where we can register custom handler, in order to give the decorator the possibility to get this handler:
Here we created a catchDecoratorStore
object with asetHandler
method which set the handler to handler
property. Then in catch
block we check if handler exists, if so — run it, otherwise — show default console message.
Now we can use it very similar to how we used Vue global error handler:
Almost finished. It’s now a fully working implementation and you can use a decorator for any method, event handler and lifecycle hook.
But, what if some method needs a separate error handling logic?. In this case we should add an additional functionality to our decorator — the ability to accept arguments, in order to use it like this: @Catch(handler)
.
The updated version:
Here the Catch
function takes a handler as an argument and returns another function which is actually a decorator. The next change made here (line 11) is checking if the handler is passed to the decorator, and if so, we call it with an error object and the context (current object, where target method is located).
Our examples context is a Vue component. If the handler didn’t pass, we’ll call a global registered handler and console a message in other case.
With these last changes you can write components like this:
That’s it, you can now use global handler or, for special cases, local handlers. It also works with any ES6/Typescript classes.
You can find all the code from this article here, clone the repository and play with the source code.
🧐 I also wrote a catch-decorator
library with the same concepts as in this article, but with some extended functionality, which you can find on Github and install from NPM:
npm install catch-decorator
Conclusion
Errors handling can be done in many ways, and you should find the best solution for your tasks and context. When a project is not very big, then moving errors handling to a separate makeRequest
function for API response errors will be enough. But, when you use class components and want to create a unified way to handle errors in your app, decorators can be helpful.
I hope this post was useful 🎓. If you have any thoughts or questions, please feel free to respond and comment below! I will be glad to answer 🙂. Thanks.
Shared with ❤️ in Bit’s blog
Components are building blocks. You are the architect.
Imagine all your components organized on the cloud, made discoverable for your team and synced in all your projects. Give it a try, it’s free & open source.