-
Star
(683)
You must be signed in to star a gist -
Fork
(110)
You must be signed in to fork a gist
-
-
Save t3dotgg/a486c4ae66d32bf17c09c73609dacc5b to your computer and use it in GitHub Desktop.
// Types for the result object with discriminated union | |
type Success<T> = { | |
data: T; | |
error: null; | |
}; | |
type Failure<E> = { | |
data: null; | |
error: E; | |
}; | |
type Result<T, E = Error> = Success<T> | Failure<E>; | |
// Main wrapper function | |
export async function tryCatch<T, E = Error>( | |
promise: Promise<T>, | |
): Promise<Result<T, E>> { | |
try { | |
const data = await promise; | |
return { data, error: null }; | |
} catch (error) { | |
return { data: null, error: error as E }; | |
} | |
} |
-
i dont think you understand when your code is type-safe. throwing errors will always throw the same error and everything regardless of its type, and the result will be correctly-typed if you checked for errors beforehand.
-
you still have to explicit state that you want a stack trace in your error instead of the solution doing it for you, that's bad dev-ex. when i am writing my app, i dont want to deal with something as trivial as error creation and handling.
- How do you know if a function throws? With this you always know the result may throw by returning an error (plus it's way faster to not capture stack trace and allocate an Error instance).
- With this you don't need stack traces. You can always throw if you want to for unrecoverable errors (you should check out
neverthrow
).
I think just like promises , it might be useful to try catch normal callback functions
export function tryCatchSync<T, E = Error>(callback: () => T): Result<T, E> {
try {
const data = callback();
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}
So now you can use it like
const {data,error} = tryCatchSync(()=>JSON.parse(someString))
For anyone who just wants to throw this function into their codebase and test it out
export function tryCatch<T, E = Error>(fn: () => T) {
type Result<TResult, EResult> =
| { data: TResult; error: null }
| { data: null; error: EResult };
type ReturnType =
T extends Promise<infer P> ? Promise<Result<P, E>> : Result<T, E>;
try {
const result = fn();
if (result instanceof Promise) {
return result
.then((data: Promise<unknown>) => ({ data, error: null }))
.catch((e: unknown) => {
return { data: null, error: e as E };
}) as ReturnType;
} else {
return { data: result, error: null } as ReturnType;
}
} catch (e: unknown) {
return { data: null, error: e as E } as ReturnType;
}
}
@osoclos Let me explain my solution
safe-throw
:An example function:
import * as st from 'safe-throw'; const fn = () => Math.random() < 0.5 ? 10 : st.err('Random');
The problem here is "how to create a safe wrapper with good DX for outside APIs that can throw". Theo's code is a solution to that. Your code is a way to control errors in your own hand-rolled APIs. These two problems are very much not the same.
@Joseph-Martre it has an API for that
import * as native from 'safe-throw/native';
const safePromise = await native.tryPromise(promise); // T | native.Err
const safeFetch = native.asyncTry(fetch);
This thread nerdsniped me to unify the sync/async methods in my lib: https://github.com/thelinuxlich/go-go-try
@osoclos
new Error().stack
. Some errors don't need a stack trace like errors thrown by a JWT verifier.if (st.isErr(result)) return result;
Or if you don't care much about perf and want a nice API:
This project is trying to be
neverthrow
but faster and uses less memory.