Tiktok: I published @jgjp/fire that lets you use .catch() and .finally() everywhere
I recently found myself being grossed out by try/catch blocks, the reasons I think boil down to:
- They are a lot of code and an extra couple blocks/indents for something that should actually be being used very often.
- Defining a variable from inside a try block always involves a
let whateverand then mutation. This adds extra complications in Typescript. - Even if you want to reuse an error handler in multiple places, it’s still 3 lines of code for your catch block to take the error and pass it to your handler.
- When using promises, we can easily chain a
.then,.catch, or even a.finallywhich is consistent with a lot of other JS syntax like.map,.reduceetc. So why can’t we do that for regular blocks of synchronous code?
It then occured to me that we can…
export const fire = async <T>(callback: () => T) =>
new Promise<T>(async (resolve, reject) => {
try {
const data = await callback()
resolve(data)
} catch (error) {
reject(error)
}
})This allows us to do stuff like this:
const { id: userId } = await fire(async () => {
setLoadingUser(true)
const { data } = await makeAPIRequest('/user')
setUser(data)
return data
})
.catch(setUserError)
.finally(() => setLoadingUser(false))
const posts = await fire(async () => {
setLoadingPosts(true)
const { data } = await makeAPIRequest('/posts', { userId })
setPosts(data)
return data
})
.catch(setPostsError)
.finally(() => setLoadingPosts(false))
// do something with postsI named the function fire because this syntax is fire (also we are firing a function).
I was inspired by the lodash attempt function. This is a drop in replacement for attempt that is slightly more wordy but maintains a familiar syntax and allows better handling of errors, as well as the .finally.
The performance is slightly worse than using a normal try/catch, but readability and maintainability are more important the vast majority of the time.
This package is now published at @jgjp/fire
