Introduction?
I feel like some patterns or shapes of error handling are more convenient than others. Depending on the context and what I needed to do when an exception occurred, I found myself handling errors differently.
And then I thought that it could be useful to write down the different ways of handling errors that I've been using and maybe experiment a bit with something new.
On this article I'll show you common ways of using data and errors, and then how I approached exception handling as is common in the Go programming language (disclaimer: I'm not a Go developer).
I'm going to be focusing on async
/Promise
s based code.
Error handling, alt 1
try {
const data = await somethingThatCanFail()
// do something with the `data`
} catch(error) {
// handle the `error`
}
Most likely the most common way of handling error nowadays.
Error handling, alt 2
somethingThatCanFail()
.then((data) => { /* do something with the `data` */ })
.catch((error) => { /* do something with the `error` */ })
Nothing new here, on the contrary, the most common way of handling errors before async
/await
became mainstream.
Error handling, alt 3
try {
await somethingThatCanFail()
.then((data) => { /* do something with the `data` */ })
} catch(error) {
// handle the `error`
}
This could be used on a way where you don't have to do much with the data
, but you have to do more than a simple action to handle the error
.
Note that even though we used .then
we need await
, otherwise if there's a problem catch
won't get it.
Error handling, alt 4
const data = await somethingThatCanFail()
.catch((error) => { /* do something with the `error` */ })
// do something with the `data`
This could be used on cases where you don't need to do much with the error, or where the error
itself doesn't need to be accessed outside of the catch
callback. It "saves" you from the extra nesting from the try
/catch
block, so it could help you handle problems more concisely.
Error handling, idea
I don't think I've seen this approach anywhere, so I wanted to experiment on whether this "shape" made sense to me.
My idea is to borrow Go's approach of handling the error as a value, which is basically:
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
You can read more about error handling in Go here: https://go.dev/blog/error-handling-and-go
I was picturing that the end result would be something like:
const [data, error] = await somethingThatCanFail()
if (error) {
// handle the `error`
}
Basically, we want our Promise
to be turned into an array
, we can do that like so:
function promiseToArray(promise) {
return promise
.then(data => [data])
.catch(error => [undefined, error]);
}
const [data, error] = await promiseToArray(somethingThatCanFail());
if (error) {
// handle the `error`
}
So, we are kind of there, but I still don't like the syntax, let's try to improve this a bit...
Promise.prototype.toGo = function() {
return this
.then(data => [data])
.catch(error => [undefined, error]);
};
const [data, error] = await somethingThatCanFail().toGo();
if (error) {
// handle the `error`
}
// or conversely
if (!error) {
// do something with the `data`
}
Now I like it much better. We are modifying the Promise
prototype, which may not be the best idea, but for an experiment looks great.
Final thoughts
Should you use this? I don't know, probably not a good idea. Personally I'd rather stick to more commonly used patterns.
This approach could be useful for some specific use case, or maybe this idea will spark some other idea that will actually be useful.
Disclaimer
While writing this, I found that someone already thought about this much earlier than me, check out this article:
https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/
Top comments (2)
Second snippet from bottom has
if (err) {
.Shouldn't it be
if (error) {
?Yes, you're right, thanks for pointing it out.