Skip to content

Instantly share code, notes, and snippets.

@panquequelol
Created June 14, 2025 00:48
Show Gist options
  • Save panquequelol/39b04a0e5cb7a8ce04a300d9c6c28e07 to your computer and use it in GitHub Desktop.
Save panquequelol/39b04a0e5cb7a8ce04a300d9c6c28e07 to your computer and use it in GitHub Desktop.
error handling with result type

Error Handling Convention

  • Intent: All fallible operations must return a Result<T, E> (from neverthrow) instead of throwing or returning null/undefined. This makes failures explicit in the type system.

  • Tagged Errors: Define every custom error as a class using Data.TaggedError("Name")<Props>. Name each error in PascalCase ending with Error. For example:

import { Data } from "effect";

export class InvalidPaymentError extends Data.TaggedError("InvalidPayment")<{
  cause: unknown;
}> {}

export class InvalidPaymentAmountError extends Data.TaggedError("InvalidPaymentAmount")<{
  amount: unknown;
}> {}
  • Fallible Functions: Functions that may fail must return Result<SuccessType, ErrorType>. Never use throw, null, or undefined to signal errors.
import { ok, err, type Result } from "neverthrow";

function parseAmount(input: string): Result<number, InvalidPaymentAmountError> {
  const value = Number(input);
  return isNaN(value)
    ? err(new InvalidPaymentAmountError({ amount: input }))
    : ok(value);
}
  • Composition & Handling: Chain multiple Result values using .andThen() or Result.combine(). Use .match() or .mapErr() to handle outcomes. Example:
const getUser = (id: string): Result<User, FetchError> => { /* ... */ };
const getProfile = (user: User): Result<Profile, ProfileError> => { /* ... */ };

const result = getUser("42").andThen(getProfile);

result.match(
  profile => console.log("Success:", profile),
  error   => console.error(`${error._tag}:`, error)
);
  • Structured Flow with Generators: Use safeTry(yield*) for linear, composable fallible workflows which compose results into a new more complex one. All yielded calls must return Result.
import { safeTry } from "neverthrow";
import { Data } from "effect";

class UserNotFoundError extends Data.TaggedError("UserNotFound")<{ userId: string }> {}
class CompanyPolicyFetchError extends Data.TaggedError("CompanyPolicyFetch")<{ companyId: string }> {}
class UserNotRegisteredError extends Data.TaggedError("UserNotRegistered")<{ userId: string, companyId: string }> {}

const getUser = (id: string): Result<User, UserNotFoundError> => { /* ... */ };
const getCompanyPolicies = (companyId: string): Result<CompanyPolicy[], CompanyPolicyFetchError> => { /* ... */ };
const checkUserInCompany = (userId: string, companyId: string): Result<true, UserNotRegisteredError> => { /* ... */ };

const result = safeTry(function* () {
  const user = yield* getUser("u123");
  const policies = yield* getCompanyPolicies(user.companyId);
  yield* checkUserInCompany(user.id, user.companyId);
  return { user, policies };
});

result.match(
  ({ user, policies }) => console.log("Access granted to", user.name),
  error => console.error(`${error._tag}:`, error)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment