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:
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)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