Errors in JavaScript

In JavaScript, error handling is typically done using exceptions, which is different from the approach used in some other languages. However, we can still implement a similar pattern of returning errors explicitly. Here's how we might structure error handling in JavaScript:

```javascript
// We can create custom error types by extending the Error class
class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = 'CustomError';
  }
}

// This function demonstrates returning an error explicitly
function f(arg) {
  if (arg === 42) {
    // Instead of using errors.New, we create a new Error object
    return [-1, new Error("can't work with 42")];
  }
  // Return null in place of nil to indicate no error
  return [arg + 3, null];
}

// We can create "sentinel" errors as constant Error objects
const ErrOutOfTea = new Error("no more tea available");
const ErrPower = new Error("can't boil water");

// This function demonstrates using and wrapping errors
function makeTea(arg) {
  if (arg === 2) {
    return ErrOutOfTea;
  } else if (arg === 4) {
    // We can "wrap" errors by creating a new error with the original as a property
    const wrappedError = new Error("making tea");
    wrappedError.cause = ErrPower;
    return wrappedError;
  }
  return null;
}

function main() {
  [7, 42].forEach(i => {
    // We use array destructuring to get the result and error
    const [r, e] = f(i);
    if (e !== null) {
      console.log("f failed:", e.message);
    } else {
      console.log("f worked:", r);
    }
  });

  for (let i = 0; i < 5; i++) {
    const err = makeTea(i);
    if (err !== null) {
      // In JavaScript, we can check the error type using instanceof
      if (err === ErrOutOfTea) {
        console.log("We should buy new tea!");
      } else if (err.cause === ErrPower) {
        console.log("Now it is dark.");
      } else {
        console.log("unknown error:", err.message);
      }
      continue;
    }
    console.log("Tea is ready!");
  }
}

main();

This code demonstrates error handling patterns in JavaScript that are similar to those used in some other languages. Here’s a breakdown of the key concepts:

  1. We create custom error types by extending the Error class.

  2. Functions that might fail return an array with the result and error. The error is null if there’s no error.

  3. We create “sentinel” errors as constant Error objects.

  4. Error wrapping is simulated by creating a new error and setting the original error as a cause property.

  5. In the main function, we use array destructuring to handle the returned arrays from f.

  6. We use === to check for specific errors, and check the cause property for wrapped errors.

When you run this code, you should see output similar to:

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 approach mimics explicit error handling in JavaScript. However, it’s worth noting that in idiomatic JavaScript, it’s more common to use try-catch blocks for error handling, especially in asynchronous code with Promises.