Skip to content

Instantly share code, notes, and snippets.

@mthomason
Created February 26, 2025 13:27
Show Gist options
  • Save mthomason/88c1706759522f6bd1fdf74098bfbc56 to your computer and use it in GitHub Desktop.
Save mthomason/88c1706759522f6bd1fdf74098bfbc56 to your computer and use it in GitHub Desktop.
A fixed version of an tryCatchWrapper for TypeScript.
// 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>;
// Unsound version of tryCatch
export async function tryCatchUnsound<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 };
}
}
// Fixed version of tryCatch
export async function tryCatchFixed<T, E = Error>(
fn: () => Promise<T>,
): Promise<Result<T, E>> {
try {
const data = await fn();
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}
// Example function that throws synchronously
function somePromiseThatThrowsSync(): Promise<string> {
throw new Error("Synchronous error!");
return Promise.resolve("This will never run");
}
// Example function that throws asynchronously
function somePromiseThatThrowsAsync(): Promise<string> {
return Promise.reject(new Error("Asynchronous error!"));
}
// Test the unsound version
async function testUnsound() {
console.log("Testing unsound version...");
try {
const { data, error } = await tryCatchUnsound(somePromiseThatThrowsSync());
if (error) {
console.error("Caught error (unsound):", error.message);
} else {
console.log("Data (unsound):", data);
}
} catch (err) {
// Narrow down the type of `err` before accessing `message`
if (err instanceof Error) {
console.error("Uncaught error (unsound):", err.message);
} else {
console.error("Uncaught unknown error (unsound):", err);
}
}
}
// Test the fixed version
async function testFixed() {
console.log("Testing fixed version...");
try {
const { data, error } = await tryCatchFixed(() => somePromiseThatThrowsSync());
if (error) {
console.error("Caught error (fixed):", error.message);
} else {
console.log("Data (fixed):", data);
}
} catch (err) {
// Narrow down the type of `err` before accessing `message`
if (err instanceof Error) {
console.error("Uncaught error (fixed):", err.message);
} else {
console.error("Uncaught unknown error (fixed):", err);
}
}
}
// Run tests
(async () => {
await testUnsound(); // This will crash with an uncaught error
await testFixed(); // This will catch the error properly
})();
@mthomason
Copy link
Author

BTW, don't do this. This is just an example on how something can be done. It's not a good idea to do this. If you find yourself rewriting core parts of the language, then you probably don't understand the language.

@squiel91
Copy link

Nice one. What do you recommend than? Try catch is kinda broken

@mthomason
Copy link
Author

The rule of them on other languages I've worked with is that every async function needs a Try/Catch block inside it, and then you can choose to propagate the error, but generally you don't want async functions to throw an error. If the function returns a promise, you could have it return a rejected promise.

This solution just seems hackish. For every function call, you are calling through this Try/Catch block, and then another anonymous function. It doesn't seem like the proper call path.

I would try to make sure that all async functions return a value. Promises have a .Catch() so the errors are designed to propagate up, but I wouldn't expect that to always be the case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment