I have the following code:
try {
phpDoc(vscode.window.activeTextEditor);
} catch (err) {
console.error(err);
vscode.window.showErrorMessage(err.message);
}
however err.message
gets the error Object is of type 'unknown'.ts(2571)
on err.
, but I cannot type the object in catch (err: Error)
.
What should I do?
The try catch in TypeScript statement provides a way to handle some or all of the errors that may occur in an application. These errors are often referred to as an exception. In a try-catch statement, you code a try block that contains the statements that may throw an exception.
The main code where an exception could arise is placed inside the try block. If an exception occurs, it goes to the catch block where it is handled; however, the catch block is skipped if no error is encountered. The finally block will always execute in any case, whether an error arises or not in the program.
unknown is the type-safe counterpart of any . Anything is assignable to unknown , but unknown isn't assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.
As a supplementary answer to CertainPerformance's one:
Up until TypeScript 4.0, the catch
clause bindings were set to any
thus allowing easy access to the message
property. This is unsafe because it is not guaranteed that what's thrown will be inheriting from the Error
prototype - it just happens that we don't throw anything but errors as best practice:
(() => {
try {
const myErr = { code: 42, reason: "the answer" };
throw myErr; //don't do that in real life
} catch(err) {
console.log(err.message); //undefined
}
})();
TypeScript 4.0 introduced an option for a safer catch
clause by allowing you to annotate the parameter as unknown
, forcing you to either do an explicit type assertion or, even better, to type guard (which makes the clause both compile-time and runtime-safe).
However, to avoid breaking most of the codebases out there, you had to explicitly opt-in for the new behavior:
(() => {
try {
throw new Error("ouch!");
} catch(err: unknown) {
console.log(err.message); //Object is of type 'unknown'
}
})();
TypeScript 4.4 introduced a new compiler option called useUnknownInCatchVariables
that makes this behavior mandatory. It is false
by default, but if you have the strict
option turned on (as you should), it is turned on which is most likely the reason why you got the error in the first place.
If you don't want to change all your code after upgrading your TypeScript but are in strict mode, you can add the following compiler option after the strict
option to overwrite it, as was hinted in Oleg's answer:
tsconfig.json
{
"compilerOptions": {
[...]
"strict": true,
"useUnknownInCatchVariables": false,
[...]
},
},
}
"strict": true,
sets useUnknownInCatchVariables
to true, and then "useUnknownInCatchVariables": false,
overrides that and sets it back to false.
It's because anything can be thrown, hence unknown
.
const fn = () => {
throw 'foo';
};
try {
fn();
} catch(e) {
console.log(e);
console.log(e instanceof Error);
console.log(e === 'foo');
}
You'll need to check that the err
actually is an error to narrow it down before accessing the message
property.
try {
phpDoc(vscode.window.activeTextEditor);
} catch (err) {
console.error(err);
if (err instanceof Error) {
vscode.window.showErrorMessage(err.message);
} else {
// do something else with what was thrown, maybe?
// vscode.window.showErrorMessage(String(err));
}
}
My TypeScript version is under 4.0, and I can't make it work again, then I created an auxiliar function to normalize the errors, like following:
interface INormalizedError {
/**
* Original error.
*/
err: unknown;
/**
* Is error instance?
*/
isError: boolean;
/**
* Error object.
*/
error?: Error;
/**
* Call stack.
*/
stack?: Error['stack'];
/**
* Error message.
*/
message: string;
toString(): string;
}
/**
* Normalize error.
*
* @param err Error instance.
* @returns Normalized error object.
*/
function normalizeError(err: unknown): Readonly<INormalizedError> {
const result: INormalizedError = {
err,
message: '',
isError: false,
toString() {
return this.message;
}
};
if (err instanceof Error) {
result.error = err;
result.message = err.message;
result.stack = err.stack;
result.isError = true;
result.toString = () => err.toString();
} else if (typeof err === 'string') {
result.error = new Error(err);
result.message = err;
result.stack = result.error.stack;
} else {
const aErr = err as any;
if (typeof err === 'object') {
result.message = aErr?.message ? aErr.message : String(aErr);
result.toString = () => {
const m = typeof err.toString === 'function' ? err.toString() : result.message;
return (m === '[object Object]') ? result.message : m;
};
} else if (typeof err === 'function') {
return normalizeError(err());
} else {
result.message = String(`[${typeof err}] ${aErr}`);
}
result.error = new Error(result.message);
result.stack = aErr?.stack ? aErr.stack : result.error.stack;
}
return result;
}
An usage example:
try {
phpDoc(vscode.window.activeTextEditor);
} catch (err) {
const e = normalizeError(err);
console.error(err);
vscode.window.showErrorMessage(e.message);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With