This handler function is so simple, yet it still has a chance to cause the program to crash.
How come? Well, based on the signature of plusOne we can't find any clue. If we look into the implementation of plusOne you'll find that it will throw an error when the given number is even.
functionplusOne(a:number){if (a %2===0) thrownewError('Given number is even.')return a +1}
Even TypeScript won't give you any hints that the function might throw errors.
You might want to add the return type manually
But the truth is that even if you do this, TypeScript still won't warn you that the code might throw errors. Check the TS Playground.
If you still don't get it, replace plusOne with JSON.parse.
If we don't read the document carefully, or the document just doesn't mention this trivial and we don't test it out during tests as well, tragedy might happen.
Try/Catch
Let's take a look at a more complex example
In this example, we need to get a lot of data from somewhere, and each step relies on the result of the above step, so they are invoked sequentially. We also want to provide fine-grained error message hints and return the function whenever an error occurs.
With these 2 problems in the mind, let take a look at how unwrapit solve this.
Just unwrapit!
The idea of unwrapit is inspired by rust. The concept behind unwrapit is simple. Program crashes because of unexpected errors being thrown out, what if we wrap them up into a box, and then let users unwrap them?
Let's take a look at how to improve the above 2 cases by using unwrapit
function plusOne(a: number): never | number {
if (a % 2 === 0) throw new Error('Given number is even.')
return a + 1
}
const ret = plusOne(1)
// ^? const ret: number
import {getUser, getNewsForUser, getRelatedNews} from 'lib'
async function handler(userInfo: {username: string, pwd: string}) {
let user
try {
user = await getUser(userInfo)
} catch {
console.error('cannot get user')
return {err: 'cannot get user')
}
let news
try {
news = await getNewsForUser(user.id)
} catch {
console.error('cannot get news for user')
return {err: 'cannot get news for user')
}
let relatedNews
try {
relatedNews = await getRelatedNews(news)
} catch {
console.error('cannot get related news')
return {err: 'cannot get related news')
}
return {data: {news, relatedNews}}
}
import {wrap} from 'unwrapit'
import {plusOne} from 'lib'
function handler() {
const wrappedPlusOne = wrap(plusOne)
const ret = wrappedPlusOne(1)
if (!ret.ok) {
console.error('error occurs', ret.err)
return
}
console.log(ret.value)
}
import {getUser, getNewsForUser, getRelatedNews} from 'lib'
import {wrap} from 'unwrapit'
const wrappedGetUser = wrap(getUser)
const wrappedGetNewsForUser = wrap(getNewsForUser)
const wrappedGetRelatedNews = wrap(getRelatedNews)
async function handler(userInfo: {username: string, pwd: string}) {
const user = await wrappedGetUser(userInfo)
if (!user.ok) {
console.error('cannot get user', user.error)
return {err: 'cannot get user')
}
const news = await wrappedGetNewsForUser(user.value.id)
if (!news.ok) {
console.error('cannot get news for user', news.error)
return {err: 'cannot get news for user')
}
const relatedNews = await wrappedGetRelatedNews(news.value)
if (!relatedNews.ok) {
console.error('cannot get related news', relatedNews.error)
return {err: 'cannot get related news')
}
return {data: {news: news.value, relatedNews: relatedNews.value}}
}