This content originally appeared on Bits and Pieces - Medium and was authored by Bartosz Salwiczek
Or why you should be careful when working with streams and event emitters in general
Example
To demonstrate this problem, I will use Node.js streams:
Stream is a structure that allows users to read/write data sequentially from/to it. Streams mainly are used when working with large amount of data. When you want to move big chunk of data from one place to another (for example from file to new file) it’s better to use streaming, so you don’t need to keep whole data in memory (it may be even impossible if data is too big). Read more.
We are creating a Writable stream (the one that only allows for writing data) and immediately after that destroying it. The destroy() function takes as a parameter error that is thrown when the stream is used after destruction. Then we try to use a stream and expect to get an error in catch block.
As you may already guess it’s not caught!
What’s going on?
The answer to this question is that stream is a subclass of EventEmitter object, which is using an asynchronous callback.
Hmm, but what does it mean? What is this EventEmitter? Why is it so special?
Event emitter
An event emitter is an object provided by Node.js to work with the events system (it’s part of the build-in events package). It allows us to send events and handle them with listeners. Code speaks more than thousands of words, so here is an example usage of a basic event emitter:
We are creating a new EventEmitter instance and attaching an event handler to 'myEvent' event. Now every time 'myEvent' is emitted, eventHandler() function will be called. Then we emit this event and expect the handler to be fired.
And the output is as expected:
What if we throw an error inside the handler? And surround everything with try-catch. Let’s see:
Awesome. It’s caught. Another experiment. Let’s transform event emitter to asynchronous event emitter. To do that just send an asynchronous function as a callback:
And after running the script we can see that error is not caught:
If you think about it, it’s a logical behavior. We are sending an asynchronous handler and in no place do we await it (nor the usage of it). The event emitter callback is called independently of the try-catch, so it’s never caught.
How do we make it not crash the application?
Handling event emitter errors
Event emitter objects give us a special event for handling the errors. Every time an error is happening inside an event emitter instance a special 'error' event is emitted. But we need to implicitly ask the event emitter to act that way by sending { captureRejections: true } inside a constructor.
All we need to do now is attach a callback to 'error' event:
No application crashes now.
Fixing a streams example
Remember the first example? It should be now easy to fix with that knowledge. For streams, we don’t need to set captureRejection: true. It’s set by default.
All that is needed is a callback on 'error' event:
Little code change that may prevent crashes of your production code in unpredictable moments.
How to avoid those unhandled errors?
Pretty simple, you just need to set up a callback for 'error' event. The real question is, how would you know if you are working with event emitter subclass?
There are a few build-in, well-known examples of event emitters:
- Streams (require('stream')) — already talked about them
- Net (require('net')) — used for networking
- CreateReadStream() from fs package ({createReadStream} = require(‘fs’)) — used to create streams from files (it falls into the streams category, but it’s so popular that I’ve decided to note it here).
Basically, all objects that emit events are instances of event emitters.
To be sure you can look into implementation and check if it extends EventEmitter or has EventEmitter in the prototype chain. Checking out the documentation will also be helpful.
Final notes
We started with a simplified, but a real-world example. Some developer used stream inside try-catch and was sure that the app will never crash. A few times a week app crashed for no reason. He was working hard to find an issue but had no idea.
“The place when a crash occurs is inside try-catch. It should be caught! Where are those errors slipping?”
Until he realized about event emitters and everything got clear instantly.
Hope you won’t do that mistake.
Event emitters are a very interesting topic. To read more check it out.
Build composable web applications
Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favorite frameworks like React or Node. Build scalable and modular applications with a powerful and enjoyable dev experience.
Bring your team to Bit Cloud to host and collaborate on components together, and speed up, scale, and standardize development as a team. Try composable frontends with a Design System or Micro Frontends, or explore the composable backend with serverside components.
Learn More
- How We Build Micro Frontends
- How we Build a Component Design System
- The Composable Enterprise: A Guide
- 7 Tools for Faster Frontend Development in 2022
When Try-Catch Doesn’t Catch Errors in Node.js was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Bartosz Salwiczek
Bartosz Salwiczek | Sciencx (2022-03-29T09:39:40+00:00) When Try-Catch Doesn’t Catch Errors in Node.js. Retrieved from https://www.scien.cx/2022/03/29/when-try-catch-doesnt-catch-errors-in-node-js/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.