Errors in TypeScript

In TypeScript, error handling is typically done using exceptions, which is different from Go’s approach. However, we can create a similar pattern using custom error types and a Result type. Here’s how we might implement the example:

// Define a custom Error type
class CustomError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "CustomError";
  }
}

// Define sentinel errors
const ErrOutOfTea = new CustomError("no more tea available");
const ErrPower = new CustomError("can't boil water");

// Define a Result type to mimic Go's multiple return values
type Result<T, E = Error> = { ok: T; err: null } | { ok: null; err: E };

function f(arg: number): Result<number> {
  if (arg === 42) {
    return { ok: null, err: new CustomError("can't work with 42") };
  }
  return { ok: arg + 3, err: null };
}

function makeTea(arg: number): Result<null> {
  if (arg === 2) {
    return { ok: null, err: ErrOutOfTea };
  } else if (arg === 4) {
    // Wrapping errors
    return { ok: null, err: new CustomError(`making tea: ${ErrPower.message}`) };
  }
  return { ok: null, err: null };
}

function main() {
  for (const i of [7, 42]) {
    const result = f(i);
    if (result.err) {
      console.log("f failed:", result.err.message);
    } else {
      console.log("f worked:", result.ok);
    }
  }

  for (let i = 0; i < 5; i++) {
    const result = makeTea(i);
    if (result.err) {
      if (result.err === ErrOutOfTea) {
        console.log("We should buy new tea!");
      } else if (result.err.message.includes(ErrPower.message)) {
        console.log("Now it is dark.");
      } else {
        console.log("unknown error:", result.err.message);
      }
      continue;
    }
    console.log("Tea is ready!");
  }
}

main();

In this TypeScript version:

  1. We define a CustomError class that extends the built-in Error class.

  2. We create sentinel errors using CustomError instances.

  3. We define a Result type that mimics Go’s multiple return values, containing either a successful value (ok) or an error (err).

  4. The f and makeTea functions return Result objects instead of using multiple return values.

  5. In the main function, we use the Result type to check for errors and handle them accordingly.

  6. To check for specific errors, we compare directly with sentinel errors or check the error message.

  7. Error wrapping is simulated by including the message of one error in another.

This approach provides a similar error-handling pattern to the Go example, allowing for explicit error checking and the use of sentinel errors. However, it’s worth noting that this is not idiomatic TypeScript/JavaScript, where try-catch blocks are more commonly used for error handling.

To run this TypeScript code, you would typically compile it to JavaScript and then run it with Node.js:

$ tsc errors.ts
$ node errors.js
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

This example demonstrates how to implement a Go-like error handling pattern in TypeScript, but remember that in real-world TypeScript applications, you would more likely use exceptions and try-catch blocks for error handling.