- Exception Handling
- Error Sub Types
- RangeError
- ReferenceError
- SyntaxError
- TypeError
- URIError
- Always use Error
- You don’t have to throw an error
- Exceptional cases
- Unclear where it is thrown
- Makes graceful handling hard
- Not well represented in the type system
- Typescript throw new error
- # Declare a function that throws an Error in TypeScript
- # never vs void
- # Functions that throw an error only some of the time
- # You can’t specify the type of error a function throws
- # Additional Resources
- Обработка исключений¶
- Подтипы ошибок¶
- RangeError¶
- ReferenceError¶
- SyntaxError¶
- TypeError¶
- URIError¶
- Всегда используйте Error ¶
- Вам не нужно выбрасывать ошибку¶
- Исключительные случаи¶
- Неясно откуда брошено исключение¶
- Делает поэтапную обработку сложной¶
- Не очень хорошо отражено в системе типов¶
Exception Handling
JavaScript has an Error class that you can use for exceptions. You throw an error with the throw keyword. You can catch it with a try / catch block pair e.g.
try < throw new Error('Something bad happened'); > catch(e) < console.log(e); >
Error Sub Types
Beyond the built in Error class there are a few additional built-in error classes that inherit from Error that the JavaScript runtime can throw:
RangeError
Creates an instance representing an error that occurs when a numeric variable or parameter is outside of its valid range.
// Call console with too many arguments console.log.apply(console, new Array(1000000000)); // RangeError: Invalid array length
ReferenceError
Creates an instance representing an error that occurs when de-referencing an invalid reference. e.g.
'use strict'; console.log(notValidVar); // ReferenceError: notValidVar is not defined
SyntaxError
Creates an instance representing a syntax error that occurs while parsing code that isn’t valid JavaScript.
1***3; // SyntaxError: Unexpectd token *
TypeError
Creates an instance representing an error that occurs when a variable or parameter is not of a valid type.
('1.2').toPrecision(1); // TypeError: '1.2'.toPrecision is not a function
URIError
Creates an instance representing an error that occurs when encodeURI() or decodeURI() are passed invalid parameters.
decodeURI('%'); // URIError: URI malformed
Always use Error
Beginner JavaScript developers sometimes just throw raw strings e.g.
try < throw 'Something bad happened'; > catch(e) < console.log(e); >
Don’t do that. The fundamental benefit of Error objects is that they automatically keep track of where they were built and originated as the stack property.
Raw strings result in a very painful debugging experience and complicate error analysis from logs.
You don’t have to throw an error
It is okay to pass an Error object around. This is conventional in Node.js callback style code which take callbacks with the first argument as an error object.
function myFunction (callback: (e?: Error)) < doSomethingAsync(function ( ) < if (somethingWrong) < callback(new Error('This is my error')) > else < callback(); >>); >
Exceptional cases
Exceptions should be exceptional is a common saying in computer science. There are few resons why this is true for JavaScript (and TypeScript) as well.
Unclear where it is thrown
Consider the following piece of code:
try < const foo = runTask1(); const bar = runTask2(); > catch(e) < console.log('Error:', e); >
The next developer cannot know which funtion might throw the error. The person reviewing the code cannot know without reading the code for task1 / task2 and other functions they might call etc.
Makes graceful handling hard
You can try to make it graceful with explicit catch around each thing that might throw:
try < const foo = runTask1(); > catch(e) < console.log('Error:', e); > try < const bar = runTask2(); > catch(e) < console.log('Error:', e); >
But now if you need to pass stuff from the first task to the second one the code becomes messy: (notice foo mutation requiring let + explicit need for annotating it because it cannot be inferred from the return of runTask1 ):
let foo: number; // Notice use of `let` and explicit type annotation try < foo = runTask1(); >catch(e) < console.log('Error:', e); > try < const bar = runTask2(foo); > catch(e) < console.log('Error:', e); >
Not well represented in the type system
function validate(value: number) < if (value < 0 || value > 100) throw new Error('Invalid value'); >
Using Error for such cases is a bad idea as it is not represented in the type definition for the validate function (which is (value:number) => void ). Instead a better way to create a validate method would be:
function validate(value: number): < if (value < 0 || value > 100) return 'Invalid value'>; >
And now its represented in the type system.
Unless you want to handle the error in a very generic (simple / catch-all etc) way, don’t throw an error.
Typescript throw new error
Last updated: Jan 22, 2023
Reading time · 3 min
# Declare a function that throws an Error in TypeScript
To declare a function that throws an error, set its return type to never .
The never type is used for functions that never return a value, in other words, functions that throw an exception or terminate execution of the program.
Copied!// 👇️ function throwErr(): never function throwErr(): never throw new Error('Something went wrong'); >
The never type is used very rarely in TypeScript.
It is used when the function is never going to reach a return statement, which happens mostly for 2 reasons:
# never vs void
If we didn’t explicitly type the function’s return value, TypeScript would have inferred it as void .
Copied!// 👇️ function throwErr(): void function throwErr() throw new Error('Something went wrong'); >
The difference between never and void is that void is used for functions that don’t return anything (or return undefined ).
If we accidentally return a value from a function that has a return type of void , we would get an error.
# Functions that throw an error only some of the time
You very rarely have to set a function’s return type to never .
For example, if the function throws an error only some of the time, you shouldn’t set its return type to never .
Copied!function sometimesThrow(): number if (Math.random() > 0.5) return 100; > throw new Error('Something went wrong'); > // 👇️ const result: number const result = sometimesThrow(); console.log(result.toFixed());
The function in the example returns a number some of the time and throws an error on some invocations.
You could use a union type to set its value to number or never .
Copied!function sometimesThrow(): number | never if (Math.random() > 0.5) return 100; > throw new Error('Something went wrong'); > // 👇️ const result: number const result = sometimesThrow(); console.log(result.toFixed());
But this is only useful to inform your colleagues that the function might throw an error.
Notice that the result variable is still typed as a number even though we set the function’s return type to number | never .
This is because every other type absorbs never in a union type.
Copied!// 👇️ type T = number type T = number | never;
Notice that number | never is simplified to number . This is the case when using never with any other type in a union.
You could be explicit and set the function’s return type to number | never because that would indicate to your colleagues that the function might throw an unhandled error and they might have to wrap the invocation in a try/catch statement.
However, this doesn’t technically influence the function’s return type.
# You can’t specify the type of error a function throws
It should also be noted that there isn’t a way for you to distinguish the type of error a function throws.
For example, if you have a class CustomError that extends from Error , there’s no way for you to tell TypeScript that your function throws a CustomError instead of an Error .
If you set the function’s return type to CustomError , this means that the function will return (and not throw) a value typed as CustomError .
# Additional Resources
You can learn more about the related topics by checking out the following tutorials:
I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
Обработка исключений¶
В JavaScript есть класс Error , который можно использовать для исключений. Вы выбрасываете ошибку с ключевым словом throw . Вы можете отловить её с помощью блоков try / catch , например:
try throw new Error('Случилось что-то плохое'); > catch (e) console.log(e); >
Подтипы ошибок¶
Помимо встроенного класса Error , существует несколько дополнительных встроенных классов ошибок, которые наследуются от Error , которые может генерировать среда выполнения JavaScript:
RangeError¶
Создается экземпляр ошибки, которая возникает, когда числовая переменная или параметр выходит за пределы допустимого диапазона.
// Вызов консоли с слишком большим количеством параметров console.log.apply(console, new Array(1000000000)); // RangeError: Невалидная длина массива
ReferenceError¶
Создается экземпляр ошибки, которая возникает при разыменовании недействительной ссылки. Например:
'use strict'; console.log(notValidVar); // ReferenceError: notValidVar не определена
SyntaxError¶
Создается экземпляр ошибки, возникающей при синтаксическом анализе кода, который не является допустимым в JavaScript.
1***3; // SyntaxError: Непредвиденный токен *
TypeError¶
Создается экземпляр ошибки, которая возникает, когда переменная или параметр имеет недопустимый тип.
'1.2'.toPrecision(1); // TypeError: '1.2'.toPrecision не является функцией
URIError¶
Создается экземпляр ошибки, которая возникает, когда в encodeURI() или decodeURI() передаются недопустимые параметры.
decodeURI('%'); // URIError: URI неправильно сформирован
Всегда используйте Error ¶
Начинающие разработчики JavaScript иногда просто бросают необработанные строки, например.
try throw 'Случилось что-то плохое'; > catch (e) console.log(e); >
Не делайте так. Основное преимущество объектов Error состоит в том, что автоматически отслеживается где они были созданы и произошли с помощью свойства stack .
Необработанные строки приводят к очень болезненной отладке и затрудняют анализ ошибок из логов.
Вам не нужно выбрасывать ошибку¶
Это нормально передавать объект Error . Это общепринятый код в Node.js колбэк стиле, который принимает колбэк первым параметром как объект ошибки.
function myFunction (callback: (e?: Error)) doSomethingAsync(function () if (somethingWrong) callback(new Error('Это моя ошибка')) > else callback(); > >); >
Исключительные случаи¶
Исключения должны быть исключительными — это частая поговорка в компьютерных науках. Это одинаково справедливо и для JavaScript (и для TypeScript) по нескольким причинам.
Неясно откуда брошено исключение¶
Рассмотрим следующий фрагмент кода:
try const foo = runTask1(); const bar = runTask2(); > catch (e) console.log('Ошибка:', e); >
Следующий разработчик не знает, какая функция может вызвать ошибку. Человек, просматривающий код, не может знать об этом, не прочитав код для task1 / task2 и других функций, которые они могут вызвать внутри себя и т.д.
Делает поэтапную обработку сложной¶
Вы можете попытаться сделать обработку поэтапной с помощью явного отлова вокруг каждого места, которое может бросить ошибку:
try const foo = runTask1(); > catch (e) console.log('Ошибка:', e); > try const bar = runTask2(); > catch (e) console.log('Ошибка:', e); >
Но теперь, если вам нужно передать что-то из первой задачи во вторую, код становится грязным: (обратите внимание на мутацию foo , требующую let + явную необходимость описывать ее, потому что это не может быть логически выведено от возврата runTask1 ):
1 2 3 4 5 6 7 8 9 10 11 12
let foo: number; // Обратите внимание на использование `let` // и явное описание типа try foo = runTask1(); > catch (e) console.log('Ошибка:', e); > try const bar = runTask2(foo); > catch (e) console.log('Ошибка:', e); >
Не очень хорошо отражено в системе типов¶
function validate(value: number) if (value 0 || value > 100) throw new Error('Невалидное значение'); >
Использование Error для таких случаев — плохая идея, так как ошибка не отражена в определении типа для проверки функции (value:number) => void . Вместо этого лучший способ создать метод проверки:
function validate(value: number): error?: string > if (value 0 || value > 100) return error: 'Невалидное значение' >; >
И теперь это отражено в системе типов.
Если вы не хотите обрабатывать ошибку очень общим (простым / универсальным и т.д.) способом, не бросайте ошибку.