Home > front end >  What is the idiomatic way to access an error's attribute in TypeScript?
What is the idiomatic way to access an error's attribute in TypeScript?

Time:11-04

TypeScript treats errors as unknown which is fair but leads me to writing what I think is ugly code. For example, to handle a particular error that I know has a "code" attribute (but I don't know the class so can't use instanceOf), I have to write this:

} catch (err) {
    if (
      typeof err === "object" &&
      err !== null &&
      "code" in err &&
      (err as { code: unknown }).code === "<some value>"
    ) {
      console.log("Special case for this particular situation")
    }
    throw err;
}

I find this ugly, too much code, and I still have to do a cast. It's tempting now to cast err to any instead but I wanna do proper TypeScript.

What's a nicer way to do this?

CodePudding user response:

I've usually done what Clashsoft showed. Just type err as any and then have a couple of guards.

But if you're doing that in a lot of places, it may be useful to have a type predicate you can reuse:

function isErrorWithCode(err: any): err is { code: string; } {
    return err && typeof err === "object" && "code" in err && typeof err.code === "string";
}

Then usage is:

try {
    // ...
} catch (err) {
    if (isErrorWithCode(err) && err.code === "<some value>") {
        console.log("Special case for this particular situation")
    }
    throw err;
}

If you really wanted to make it convenient, you could combine the type predicate with a function that checks for the given error code:

function errorHasCode(err: any, code: string) {
    return isErrorWithCode(err) && err.code === code;
}

then:

try {
    // ...
} catch (err) {
    if (errorHasCode(err, "<some value>")) {
        console.log("Special case for this particular situation")
    }
    throw err;
}

I have that in my Excel JavaScript add-in codebase for convenience. (The Excel JavaScript API sometimes throws errors with code property that's a string.)

CodePudding user response:

If all you need is the code, I would go with this:

} catch (err: any) {
  if (err?.code === 'whatever') {
    console.log("Special case for this particular situation")
  }
  throw err;
}

If err is null, the optional chain expression will yield undefined, which is probably not your whatever. Same result if err does not have a code property.

CodePudding user response:

You could transform your error in a class, throw it and then check for the error with instanceof

try {
  throw new MyError({ code: "Hello, world" });
} catch (e) {
  if (e instanceof MyError) {
    console.log(e.code);
  }
}

Otherwise you could use some libs like Zod to validate it but introducing Zod just for this could be a little overkill.

CodePudding user response:

Isn't it a case of either:

  1. Knowing the type of the error in advance (in which case you can safely construct an interface/type to reflect that, if one is not available, and type-cast with as to guarantee future type safety within the block)

  2. You don't know the type, but you know the property(s) you're interested in, in which case a solution like yours is somewhat necessary as you're trying to get from any possible type (e.g. any / unknown) to a specific subtype that the TS compiler will understand and be able to adhere to within block

A halfway house solution could be to define a type with the information you do know and omit the rest, e.g.

interface SpecificError {
  code: string,
  // ...
}

} catch (err) {
  const eTyped: SpecificError = e as SpecificError 

  eTyped.code // valid
}

or

} catch (err) {
  const eTyped: { code: string } = e as { code: string }

  eTyped.code // valid  
}

A slightly shorter way of implementing option 2 given the lack of information available to implement option 1.

Edit: @t-j-crowder's solution using type-guards is also an elegant way to achieve the same idea. Leaving this answer up in case the "quick but dirty" approach is also of value

  • Related