Skip to content

Error Handling

Building reliable apps isn't just about making things work. It’s about making sure things fail gracefully when they don’t. That’s where Effect comes in — a modern, composable effect system for TypeScript and JavaScript — aligning with Zap.ts' philosophy.

Why use Effect?

Most people sprinkle try/catch or .catch() throughout their code — and it works… until it doesn’t. You end up with inconsistent error handling, unreadable logic, and debugging nightmares.

Effect fixes that by giving you:

  • Better testability — effects are easy to simulate/mock
  • Composable workflows — you can chain logic clearly
  • Explicit error handling — no hidden surprises
  • Type-safe async code — TypeScript actually helps you

Instead of letting errors blow up in random places, you model them as data. That makes your app’s behavior predictable and easier to reason about.

A simple example

Here’s how you’d wrap an async API call using Effect.tryPromise, it's pretty similar to what you already know.

ts
import { Effect } from "effect";

const fetchUser = (id: string) =>
  Effect.tryPromise({
    try: async () => {
      const res = await fetch(`/api/users/${id}`);
      if (!res.ok) throw new Error("User not found");
      return res.json();
    },
    catch: (err) => err,
  });

Effect.runPromise(fetchUser("123"))
  .then((user) => {
    // handle user
  })
  .catch((error) => {
    // handle error
  });

Effect.tryPromise lets you safely run async code without random crashes. Everything is contained and type-checked.

Handling Errors Declaratively

You can also cleanly handle errors as part of your logic, without nested .then()s or try/catch blocks. Here’s how:

ts
import { Effect } from "effect";

const safeFetch = fetchUser("123").pipe(
  Effect.catchAll((error) => Effect.succeed({ error }))
);

Effect.runPromise(safeFetch).then((result) => {
  if ("error" in result) {
    // handle error
  } else {
    // handle success
  }
});

What’s happening here? Let's try to understand the above code.

  1. You try to fetch the user.
  2. If it fails, you catch the error and return it as a regular value.
  3. Now everything — success and failure — is part of the same flow.
  4. No surprises, no runtime crashes.

Learn More

We recommend you to check the Effect Documentation to learn more. While it can be a mind shift, once you get it, your code will be way more predictable.

Released under the MIT License.