Return throw new error

JavaScript поддерживает компактный набор инструкций, особенно управляющих инструкций, которые вы можете использовать, чтобы реализовать интерактивность в вашем приложении. В данной главе даётся обзор этих инструкций.

Порядок выполнения и обработка ошибок

  • « Предыдущая статья
  • Следующая статья »

JavaScript поддерживает компактный набор инструкций, особенно управляющих инструкций, которые вы можете использовать, чтобы реализовать интерактивность в вашем приложении. В данной главе даётся обзор этих инструкций.

Более подробная информация об инструкциях, рассмотренных в данной главе, содержится в справочнике по JavaScript. Точка с запятой (;) используется для разделения инструкций в коде.

Любое выражение (expression) в JavaScript является также инструкцией (statement). Чтобы получить более подробную информацию о выражениях, прочитайте Выражения и операторы.

Инструкция block

Инструкция block является фундаментальной и используется для группировки других инструкций. Блок ограничивается фигурными скобками:

{ statement_1; statement_2; ... statement_n; }

Блок обычно используется с управляющими инструкциями (например, if, for, while).

В вышеприведённом примере { x++; } является блоком.

Обратите внимание: в JavaScript отсутствует область видимости блока до ECMAScript2015. Переменные, объявленные внутри блока, имеют область видимости функции (или скрипта), в которой находится данный блок, вследствие чего они сохранят свои значения при выходе за пределы блока. Другими словами, блок не создаёт новую область видимости. «Автономные» (standalone) блоки в JavaScript могут продуцировать полностью отличающийся результат, от результата в языках C или Java. Например:

var x = 1;
{
  var x = 2;
}
console.log(x); // выведет 2

В вышеприведённом примере инструкция var x внутри блока находится в той же области видимости, что и инструкция var x перед блоком. В C или Java эквивалентный код выведет значение 1.

Начиная с ECMAScript 6, оператор let позволяет объявить переменную в области видимости блока. Чтобы получить более подробную информацию, прочитайте let.

Условные инструкции

Условная инструкция — это набор команд, которые выполняются, если указанное условие является истинным. JavaScript поддерживает две условные инструкции: if...else и switch.

Инструкция if…else

Используйте оператор if для выполнения инструкции, если логическое условия истинно. Используйте опциональный else, для выполнения инструкции, если условие ложно. Оператор if выглядит так:

if (condition) {
    statement_1;
} else {
    statement_2;
}

Здесь condition может быть любым выражением, вычисляемым как истинное (true) или ложное (false). Чтобы получить более подробную информацию о значениях true и false, прочитайте Boolean. Если условие оценивается как true, то выполняется statement_1, в противном случае — statement_2. Блоки statement_1 и statement_2 могут быть любыми блоками, включая также вложенные инструкции if.

Также вы можете объединить несколько инструкций, пользуясь else if для получения последовательности проверок условий:

if (condition_1) { statement_1;} else if (condition_2) { statement_2;} else if (condition_n) { statement_n; } else { statement_last;}

В случае нескольких условий только первое логическое условие, которое вычислится истинным (true), будет выполнено. Используйте блок ({ ... }) для группировки нескольких инструкций. Применение блоков является хорошей практикой, особенно когда используются вложенные инструкции if:

if (condition) {
  statement_1_runs_if_condition_is_true;
  statement_2_runs_if_condition_is_true;
} else {
  statement_3_runs_if_condition_is_false;
  statement_4_runs_if_condition_is_false;
}

Нежелательно использовать простые присваивания в условном выражении, т.к. присваивание может быть спутано с равенством при быстром просмотре кода. Например, не используйте следующий код:

Если вам нужно использовать присваивание в условном выражении, то распространённой практикой является заключение операции присваивания в дополнительные скобки. Например:

if ( (x = y) ) { /* ... */ }

Ложные значения

Следующие значения являются ложными:

  • false
  • undefined
  • null
  • 0
  • NaN
  • пустая строка ( "" )

Все остальные значения, включая все объекты, будут восприняты как истина при передаче в условное выражение.

Не путайте примитивные логические значения true и false со значениями true и false объекта Boolean. Например:

var b = new Boolean(false);
if (b) // это условие true
if (b == true) // это условие false

В следующем примере функция checkData возвращает true, если число символов в объекте Text равно трём; в противном случае функция отображает окно alert и возвращает false.

function checkData() {
  if (document.form1.threeChar.value.length == 3) {
    return true;
  } else {
    alert("Enter exactly three characters. " +
    document.form1.threeChar.value + " is not valid.");
    return false;
  }
}

Инструкция switch

Инструкция switch позволяет сравнить значение выражения с различными вариантами и при совпадении выполнить соответствующий код. Инструкция имеет следующий вид:

switch (expression) {
   case label_1:
      statements_1
      [break;]
   case label_2:
      statements_2
      [break;]
   ...
   default:
      statements_default
      [break;]
}

Сначала производится поиск ветви case с меткой label, совпадающей со значением выражения expression. Если совпадение найдено, то соответствующий данной ветви код выполняется до оператора break, который прекращает выполнение switch и передаёт управление дальше. В противном случае управление передаётся необязательной ветви default и выполняется соответствующий ей код. Если ветвь default не найдена, то программа продолжит выполняться со строчки, следующей за инструкцией switch. По соглашению ветвь default является последней ветвью, но следовать этому соглашению необязательно.

Если оператор break отсутствует, то после выполнения кода, который соответствует выбранной ветви, начнётся выполнение кода, который следует за ней.

В следующем примере если fruittype имеет значение "Bananas", то будет выведено сообщение "Bananas are $0.48 a pound." и оператор break прекратит выполнение switch. Если бы оператор break отсутствовал, то был бы также выполнен код, соответствующий ветви "Cherries", т.е. выведено сообщение "Cherries are $3.00 a pound.".

switch (fruittype) {
  case "Oranges":
    console.log("Oranges are $0.59 a pound.");
    break;
  case "Apples":
    console.log("Apples are $0.32 a pound.");
    break;
  case "Bananas":
    console.log("Bananas are $0.48 a pound.");
    break;
  case "Cherries":
    console.log("Cherries are $3.00 a pound.");
    break;
  case "Mangoes":
    console.log("Mangoes are $0.56 a pound.");
    break;
  case "Papayas":
    console.log("Mangoes and papayas are $2.79 a pound.");
    break;
  default:
   console.log("Sorry, we are out of " + fruittype + ".");
}
console.log("Is there anything else you'd like?");

Инструкции обработки исключений

Инструкция throw используется, чтобы выбросить исключение, а инструкция try...catch, чтобы его обработать.

Типы исключений

Практически любой объект может быть выброшен как исключение. Тем не менее, не все выброшенные объекты создаются равными. Обычно числа или строки выбрасываются как исключения, но часто более эффективным является использование одного из типов исключений, специально созданных для этой цели:

  • Исключения ECMAScript
  • DOMException (en-US) и DOMError (en-US)

Инструкция throw

Используйте инструкцию throw, чтобы выбросить исключение. При выбросе исключения нужно указать выражение, содержащее значение, которое будет выброшено:

throw expression;

Вы можете выбросить любое выражение, а не только выражения определённого типа. В следующем примере выбрасываются исключения различных типов:

throw "Error2";                                              // string
throw 42;                                                    // number
throw true;                                                  // boolean
throw { toString: function() { return "I'm an object!"; } }; // object

Примечание: Вы можете выбросить объект как исключение. Вы можете обращаться к свойствам данного объекта в блоке catch.

Примечание: В следующем примере объект UserException выбрасывается как исключение:

function UserException (message) {
  this.message = message;
  this.name = "UserException";
}

UserException.prototype.toString = function () {
  return this.name + ': "' + this.message + '"';
}

throw new UserException("Value too high");

Инструкция try…catch

Инструкция try...catch состоит из блока try, который содержит одну или несколько инструкций, и блок catch, которые содержит инструкции, определяющие порядок действий при выбросе исключения в блоке try. Иными словами, если в блоке try будет выброшено исключение, то управление будет передано в блок catch. Если в блоке try не возникнет исключений, то блок catch будет пропущен. Блок finally будет выполнен после окончания работы блоков try и catch, вне зависимости от того, было ли выброшено исключение.

В следующем примере вызывается функция getMonthName, которая возвращает название месяца по его номеру. Если месяца с указанным номером не существует, то функция выбросит исключение "InvalidMonthNo", которое будет перехвачено в блоке catch:

function getMonthName(mo) {
  mo = mo - 1; // Adjust month number for array index (1 = Jan, 12 = Dec)
  var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul",
                "Aug","Sep","Oct","Nov","Dec"];
  if (months[mo]) {
    return months[mo];
  } else {
    throw "InvalidMonthNo"; //throw keyword is used here
  }
}

try { // statements to try
  monthName = getMonthName(myMonth); // function could throw exception
}
catch (e) {
  monthName = "unknown";
  logMyErrors(e); // pass exception object to error handler -> your own
}

Блок catch

Используйте блок catch, чтобы обработать исключения, сгенерированные в блоке try.

catch (catchID) { statements }

JavaScript создаёт идентификатор catchID, которому присваивается перехваченное исключение, при входе в блок catch; данный идентификатор доступен только в пределах блока catch и уничтожается при выходе из него.

В следующем примере выбрасывается исключение, которое перехватывается в блоке catch:

try {
  throw "myException"
} catch (e) {
  console.error(e);
}

Блок finally

Блок finally содержит код, который будет выполнен после окончания работы блоков try и catch, но до того, как будет выполнен код, который следует за инструкцией try...catch. Блок finally выполняется вне зависимости от того, было ли выброшено исключение. Блок finally выполняется даже в том случае, если исключение не перехватывается в блоке catch.

В следующем примере открывается файл, затем в блоке try происходит вызов функции writeMyFile, который может выбросить исключение. Если возникает исключение, то оно обрабатывается в блоке catch. В любом случае файл будет закрыт функцией closeMyFile, вызов которой находится в блоке finally.

openMyFile();
try {
  writeMyFile(theData);
} catch(e) {
  handleError(e);
} finally {
  closeMyFile();
}

Если блок finally возвращает значение, то данное значение становится возвращаемым значением всей связки try-catch-finally. Значения, возвращаемые блоками try и catch, будут проигнорированы.

function f() {
  try {
    console.log(0);
    throw "bogus";
  } catch(e) {
    console.log(1);
    return true;    // приостанавливается до завершения блока `finally`
    console.log(2); // не выполняется
  } finally {
    console.log(3);
    return false;   // заменяет предыдущий `return`
    console.log(4); // не выполняется
  }
  // `return false` выполняется сейчас
  console.log(5);  // не выполняется
}
f();               // отображает 0, 1, 3 и возвращает `false`

Замена возвращаемых значений блоком finally распространяется в том числе и на исключения, которые выбрасываются или перевыбрасываются в блоке catch:

function f() {
  try {
    throw "bogus";
  } catch(e) {
    console.log('caught inner "bogus"');
    throw e;      // приостанавливается до завершения блока `finally`
  } finally {
    return false; // заменяет предыдущий `throw`
  }
  // `return false` выполняется сейчас
}

try {
  f();
} catch(e) {
  // Не выполняется, т.к. `throw` в `catch `заменяется на `return` в `finally`
  console.log('caught outer "bogus"');
}

// В результате отображается сообщение caught inner "bogus"
// и возвращается значение `false`

Вложенные инструкции try...catch

Вы можете вкладывать инструкции try...catch друг в друга. Если внутренняя инструкция try...catch не имеет блока catch, то она должна иметь блок finally, кроме того исключение будет перехвачено во внешнем блоке catch. Для получения большей информации ознакомьтесь с вложенными try-блоками.

Использование объекта Error

В зависимости от типа ошибки вы можете использовать свойства name и message, чтобы получить более подробную информацию. Свойство name содержит название ошибки (например, DOMException или Error), свойство message — описание ошибки.

Если вы выбрасываете собственные исключения, то чтобы получить преимущество, которое предоставляют эти свойства (например, если ваш блок catch не делает различий между вашими исключениями и системными), используйте конструктор Error. Например:

function doSomethingErrorProne () {
  if ( ourCodeMakesAMistake() ) {
    throw ( new Error('The message') );
  } else {
    doSomethingToGetAJavascriptError();
  }
}

try {
  doSomethingErrorProne();
} catch (e) {
  console.log(e.name);    // 'Error'
  console.log(e.message); // 'The message' или JavaScript error message
}

Объект Promise

Начиная с ECMAScript2015, JavaScript поддерживает объект Promise, который используется для отложенных и асинхронных операций.

Объект Promise может находиться в следующих состояниях:

  • ожидание (pending): начальное состояние, не выполнено и не отклонено.
  • выполнено (fulfilled): операция завершена успешно.
  • отклонено (rejected): операция завершена с ошибкой.
  • заданный (settled): промис выполнен или отклонен, но не находится в состоянии ожидания.

Загрузка изображения при помощи XHR

Простой пример использования объектов Promise и XMLHttpRequest для загрузки изображения доступен в репозитории MDN promise-test на GitHub. Вы также можете посмотреть его в действии. Каждый шаг прокомментирован, что позволяет вам разобраться в архитектуре Promise и XHR. Здесь приводится версия без комментариев:

function imgLoad(url) {
  return new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url);
    request.responseType = 'blob';
    request.onload = function() {
      if (request.status === 200) {
        resolve(request.response);
      } else {
        reject(Error('Image didn't load successfully; error code:'
                     + request.statusText));
      }
    };
    request.onerror = function() {
      reject(Error('There was a network error.'));
    };
    request.send();
  });
}
  • « Предыдущая статья
  • Следующая статья »

When you design a function interface and there are errors to deal with, you have a design choice for how to return errors. If the function is synchronous, you can either return some sentinel value that indicates an error and is easily distinguished from an actual result (often null in Javascript) or you can throw an exception or you can return an object that has a property that indicates the success or failure of the operation.

When you have an asynchronous operation with a promise interface, one would usually reject the Promise with an Error object as the reject reason to signify an error. That’s the core design theory of promises. Success resolves with an optional value, errors reject with a reason.

This block of code:

return dbstore
  .getItem(key)
  .then(function(value) {
    return value;
  })
  .catch(function(err) {
    return new Error('The key (' + key + ") isn't accessible: " + err);
  });

is resolving the returned promise with either a value or an Error object. This is generally not how promise code is written because it will require the caller to test the type of the resolved value to figure out if there’s an error or not which is not the simple, straightforward way to use promises. So, to your question, you would usually do this:

return dbstore.getItem(key).catch(function(err) {
    throw new Error('The key (' + key + ") isn't accessible: " + err);
});

There are other signs in this function, that it’s just bad code.

  1. .then(function(value) {return value;}) is completely superfluous and unnecessary. It adds no value at all. The value is already the resolved value of the promise. No need to declare it again.

  2. The function sometimes returns a promise and sometimes throws a synchronous exception.
    This is even a further pain to use. If you look at the first if (!key) { statement, it returns an Error object is the key argument isn’t supplied. That means that to use this function you have to catch synchronous exceptions, provide .then() and .catch() handlers AND check the type of the resolved promise to see if it happens to be an error object. This function is a nightmare to use. It’s bad code.

To use the function as it is, the caller would likely have to do this:

let retVal = someObj.get(aKey);
if (typeof retVal === Error) {
    // got some synchronous error
} else {
    retVal.then(val => {
        if (typeof val === Error) {
            // got some asynchronous error
        } else {
            // got an actual successful value here
        }
    }).catch(err => {
        // got some asynchronous error
    })
}

The function implementation probably should be this:

get(key, store = null) {
    if (!key) {
        return Promise.reject(new Error('There is no key to get!'));
    }

    let dbstore = store || this.localforage;

    return dbstore.getItem(key).catch(function(err) {
        throw new Error('The key (' + key + ") isn't accessible: " + err);
    });
}

This can then be used like this:

someObj.get(aKey).then(val => {
    // got some successful value here
}).catch(err => {
    // got some error here
});

Compare the simplicity for the caller here to the mess above.

This implementation has these consistencies:

  1. It always returns a promise. If key isn’t supplied, it returns a rejected promise.
  2. All errors come via a rejected promise
  3. The value the promise resolves with is always an actual successful value
  4. There’s no .then() handler that does nothing useful.

JavaScript return Vs throw Error

The return and throw are used to handle errors and exceptions in JavaScript, but they are used in different ways.

The return statement is used to exit a function and return a value to the caller. It can be used to return a value, or to exit a function without returning a value.

On the other hand, the throw statement is used to signal an exception or error. It can be used to throw a user-defined exception. When the throw statement is used, the normal flow of the program is interrupted and the program jumps to the nearest catch block.

The main difference between return and throw is that a return statement is used to exit a function and return a value, while a throw statement is used to signal an error or exception.

A return statement should be used to end a function and return a value to the calling code, whereas a throw statement should be used to indicate that an error has occurred and that the function cannot continue executing normally.

In addition, throw statement can be caught with try...catch block, while returned error cannot be caught this way.

ALSO READ: How to redirect with JavaScript? [SOLVED]

It’s generally recommended to use throw statement to signal an exception, and return statement to return the value or exit a function.

Using return statement

The return statement is used to exit a function and return a value to the caller. First, let’s show how we can use the return statement to return a value, especially within a function. The value returned can be of any data type, including numbers, strings, objects, and even other functions. For example, a simple function that returns the square of a number might look like this

function square(x) {
    return x * x;
}
console.log(square(256));

Output

65536

In addition, aside from returning a value, we can make use of the return statement to exit the function execution early, even if there are more statements to be executed in the function. This is useful in situations where a certain condition is met and you don’t want to execute the remaining statements.

function isAdult(age) {
    if (age < 18) {
        return "Not an adult.";
    }
    return "Adult.";
}
console.log(isAdult(15));

Output

Not an adult.

In the above code, we call the isAdult function with the value of 15, the function returns "Not and adult." because the value is less than 18 and doesn’t even reach the other return statement.

In this example, the divide() function checks if the divisor (b) is equal to zero, if so it returns an error object with a message «Cannot divide by zero». The returned value is then checked if it’s an instance of error if true it logs the error message, otherwise it logs the result.

function divide(a, b) {
    if (b === 0) {
        return new Error('Cannot divide by zero');
    }
    return a / b;
}

let result = divide(4, 2);
if (result instanceof Error) {
    console.log(result.message);
} else {
    console.log(result);
}

ALSO READ: Solved: Node.js create directory if doesn’t exist [Examples]

Using throw statement

The throw statement is used to throw an exception, which is a special object that represents an error or exceptional condition. When an exception is thrown, the current function execution is stopped and control is passed to the nearest catch block, which can handle the exception. For example, a simple function that checks if a number is positive might look like this

function checkPositive(x) {
    if (x < 0) {
        throw "Error: Number is not positive";
    }
    return "Number is positive";
}
console.log(checkPositive(-34));

Output

throw "Error: Number is not positive";
        ^
Error: Number is not positive

In this example, the function throws an exception when the input number is not positive.

Furthermore, throw statements are used in conjunction with try/catch statement to deal with errors. So in a situation where your functions throw an exception, the catch section of the statement will handle the exception. Let’s illustrate this by creating a divide() function that will throw an error when we try to divide a number by 0.

function divide(a, b) {
    if (b === 0) {
        throw new Error("Cannot divide by zero.");
    }
    return a / b;
}

try {
    let result = divide(5, 0);
    console.log(result);
} catch (e) {
    console.error(e);
}

Output

Error: Cannot divide by zero.

In this example, the divide function checks if the divisor (b) is zero. If it is, an error is thrown using the throw statement with the message «Cannot divide by zero.» The try/catch block is used to handle the exception. The code inside the try block is executed and if an exception is thrown, the code inside the catch block is executed instead. In this case, the error message is logged into the console.

ALSO READ: JavaScript Arrow Function [In-Depth Tutorial]

If we don’t want to log the error, we can simply log a statement in place of the error message.

try {
    let result = divide(5, 0);
    console.log(result);
} catch (e) {
    console.log("Cannot divide by zero");
}

Output

Cannot divide by zero

JavaScript provides several built-in error objects that developers can use to create more informative error messages. These include Error, TypeError, RangeError, SyntaxError, and others.

Summary

In summary, JavaScript developers use return statement to exit a function and return a value to the caller, and throw statement to throw an exception, which represents an error or exceptional condition. return statement is used to exit the function execution early, even if there are more statements to be executed in the function. throw statement is used to throw an exception and stop the current function execution and pass the control to the nearest catch block which can handle the exception.

References

return — JavaScript | MDN (mozilla.org)
throw — JavaScript | MDN (mozilla.org)

Ришат Габайдуллов

Ришат Габайдуллов


руководитель группы практики Frontend компании «Рексофт»

Тема обработки ошибок в JavaScript возникает не только у каждого новичка, но и матерого разработчика. Замечу, что тема уже довольно заезжена, поэтому я позволю себе резюмировать в кратком изложении все, что действительно эффективно и проверено в бою мною, коллегами и гуру IT.

Не погружаясь в этимологию ошибки в JavaScript, охарактеризуем ее абстрактно, поскольку сам по себе объект ошибки в JS не стандартизирован полностью.

Ошибка в JS — это «выбрасывание» исключения (throw of an exception). Исключение должно быть обработано программой, в противном случае интерпретатор вернет нас на то место, где это исключение было выброшено. По умолчанию исключение выбрасывает объект Error.

Неважно, пишете ли вы Frontend или Backend, подход к обработке один – поймать злосчастное исключение и обработать. Обрабатывать нужно все, особенно в проде.

Сразу просветим пару нестандартных ситуаций:

  • Ошибка извне программы
  • Терминальная ошибка

Терминальная ошибка – это код ошибки, который возвращает ОС или демон.

Ошибка извне программы может быть частным случаем терминальной, но тем не менее она должна быть обработана.

Любая из этих нестандартных ситуаций может попасть в общий стек ошибок и будет обработана, т.к. каждое выброшенное исключение, попавшее в программу, захватывает стек.

Самый главный вопрос – когда возникает ошибка?

Ошибка возникает в том случае, когда программа или интерпретатор не может перейти к следующей инструкции по некоторым причинам:

  • синтаксическая ошибка (забыли запятую, скобку и т.д.);
  • ошибка интерпретатора (обращение к несуществующей переменной и т.д.);
  • ошибка исполнения (тип переменной оказался, например, undefined) – самая частая в работающем приложении;
  • и еще несколько вариантов, с которыми вы можете ознакомиться тут.

В каждом из случаев есть человеческий фактор. Чтобы этого не допускать используйте линтеры, которые следят за чистотой вашего кода и минимизируют риск возникновения ошибок еще до запуска. Вдобавок к этому, ошибки исполнения – частое явление в JavaScript, следить за этим помогает Typescript.

Железобетонные методы обработки ошибок

Чтобы сражаться с врагом, нужно знать его в лицо, поэтому ниже основные свойства объекта Error:

  • name – название ошибки;
  • message – текст выбрасываемой ошибки;
  • stack – стек вызовов, приведших к ошибке.

Важно заметить, что свойства не стандартизированы. Также важно помнить, что исключение может быть любым типом данных.

Из этого набора информации при обработке ошибок самым важным является их классификация. По моему мнению, если удалось правильно классифицировать выброшенное исключение – это 80% работы. Остальные 20% завязаны на правильной обработке, ведь каждое приложение – это бизнес, следовательно минимизация ошибок в бизнесе – прирост конверсии.

В зависимости от приложения классификация и обработка могут быть написаны собственноручно, либо можно задействовать готовые инструменты. Мы не будем рассматривать производные методы отлова ошибок, такие как TDD или E2E, а ограничимся только девелоперскими инструментами, но прежде определим, что мы желаем получить от инструмента:

  • стек вызовов, приведших к ошибке;
  • уровень ошибки (фатальная, критическая, баг, неожиданная и т.д.);
  • класс ошибки (сетевая, сервисная, пользовательская и т.д.);
  • хранение ошибки для анализа и пост-обработки;
  • логирование;
  • профилирование / метрика.

Из всего обилия существующих инструментов выбирать нужно именно по этим критериям. Разница инструментов, как правило, в том, что одни заточены под разработку приложения, а другие под работу приложения в релизе. Суть одна, но какому-то из критериев просто уделяется больше внимания.

Уметь выбирать библиотеку – отличный навык, а умение компоновать поможет вам приблизиться к идеалу. Предлагаю разобрать способы именно для процесса разработки.

Придерживаясь методологий SOLID и DRY, нам следует внедрить наш обработчик (middleware) на самый верхний уровень и уже оттуда обрабатывать все ошибки, которые прошли мимо. Middleware может быть как написанный самостоятельно, так и из библиотеки. Ниже примеры.

  • Для Node.js
  • Для Vanilla JS
  • Для React
  • Для Angular
  • Для Vue

Данные примеры касаются верхнеуровнего отлова и обработки ошибок, но возникает резонный вопрос: как быть с частными случаями, встречающимися в парадигме JavaScript? Ниже несколько примеров.

Всегда оборачивайте асинхронный код в try…catch, а также вызовы сторонних библиотек. Например, вот так:

// ...

const middlewareRequest = async (req) => {
  try {
    const { data } = await axios.get(req);
    
    return data;
  } catch (err) {
    throw new Error(err);
  }
}

// ...

Опытный архитектор может заметить, что если оборачивать все асинхронные конструкции в try…catch, то это сродни «аду коллбэков», поэтому придерживайтесь методологии DRY и пишите все на верхнем уровне, если позволяет ваша архитектура.

То же касается и работы с событийной моделью: можно назначать middleware через Функции Высшего Порядка – в будущем это позволит вам быстро масштабироваться.

// ...

const wrapEventWithExcpetionHandler = (middleware) => (e) => {
  const { error } = e; // предположим, что ошибка в этом поле
  
  if (error) {
    throw new Error(error);
  }
  
  try {
    return middleware(e);
  } catch (err) {
    throw new Error(err);
  }
}

window.addEventListener('mousemove', wrapEventWithExceptionHandler(middlewareGlobalMouseMove));

// ...

Как видно из примеров выше, следуя путем самостоятельной классификации, разработчику придется сильно напрячься и писать велосипед для каждой библиотеки или фреймворка, следовательно, эффективнее выбирать готовый инструмент в зависимости от потребностей.

В любом случае эти примеры могут быть вам полезны, поскольку внедрение даже готового инструмента подразумевает «поднятие» обработчиков на верхний уровень.

Еще раз взглянув на примеры, можно удостовериться в одном: любая пойманная ошибка в идеале должна иметь одинаковый набор параметров информативности независимо от инструмента и среды исполнения, а ведь мы еще не рассмотрели логирование, профилирование и хранение ошибок на сервере. Соответственно, из этого можно сделать вывод, что слой обработки ошибок должен сосуществовать со слоем исполнения, а это уже влечет за собой последствия и накладывается на архитектуру.

Так для чего же нужны эти приемы?

Ответ – для ведения простой и надежной разработки. Ваша задача как разработчика – делать отказоустойчивый код и при возникновении ошибки не дебажить все подряд, а сразу бить в «яблочко» и устранять проблему. Это попросту экономит ваше время, силы и деньги бизнеса.

Работайте с DevTools и выбрасывайте исключения, другие разработчики будут вам благодарны, опираясь на этот гайд. Обязательно ознакомьтесь, если не знали, вот пример:

// ...

/* обычный console.log может превратиться в нечто большее */

/*
  как правило, начинающие программисты логируют по одной переменной,
  мы же можем форматировать строки с любым количеством аргументов
*/

console.log('Check:rn  username - %srn  age - %irn  data - %o', 'Mike', 23, {status: 'registered'});
/*
Check:
  username - Mike
  age - 23
  data - {status: "registered"}
*/

/* выводить таблицы массивов */
console.table([{username: 'Mike', age: 23}, {username: 'Sarah', age: 46}]);

/* или просто логировать данные в их первоначальном виде */
console.dir(document.body.childNodes[1]);

// ...

Далее рассмотрим инструменты, которые собирают данные не только от компонентов, но и от сервисов (например, сетевые запросы, запросы к устройству и т.д.), а также сторонних библиотек и приложений, что кратно улучшает вашу производительность при обработке ошибок.

Облегчаем себе жизнь

  1. Рекомендую взять за правило: перед началом каждой разработки централизовать любое логирование, особенно ошибок. С этой задачей помогут справиться библиотеки по типу log4js. Это сразу даст вам понять, ошибка в вашем приложении, либо извне.
  2. Используйте Брейкпоинты в DevTools! Это важно уметь делать. Это как машина времени программы, вы останавливаете интерпретатор на нужной строчке и вам даже не нужна консоль – просто смотрите значения переменных и поймете, что не так. Делается это простым кликом на нужной строчке во вкладке Source. Выбираете нужный файл, ставите брейкпоинт и перезапускаете программу. Для удаления брейкпоинта кликните на ту же строчку.
  3. Старайтесь перехватывать все ошибки и исключения на верхнем уровне.
  4. Хранение ошибок на сервере больше относится к проду, но имейте в виду, что готовый инструмент прекрасно справляется с данной задачей (см. ниже).
  5. Профилирование – тема тоже непростая, если вы знаете, что это измерение времени от начала до конца исполнения монады, вы уже на полпути. К счастью, DevTools позволяют делать замеры без вмешательства в код.

Эти правила априори необходимы, даже если вы опытный разработчик, вам всегда нужно знать, как ведет себя программа в конкретный момент времени.

Для ПРОДвинутых

Если вы уже как рыба в воде при работе с ошибками в JS, рекомендую посмотреть на сервисы для автоматизации сбора и ведения статистики ошибок.

Такие сервисы, как Sentry и Rollbar, уже заточены под работу со многими популярными пакетами и требуют от вас только установки, задания конфигурации в точке входа вашего приложения и определение надстроек в зависимости от вашей архитектуры.

Их преимущество в том, что они покрывают большую часть потребностей, о которых написано выше и требуют вашего минимального вмешательства. На выходе вы получите приятные бонусы с красивым интерфейсом и все пункты, которые я указал важными (Железобетонные методы обработки ошибок).

Также рекомендую ознакомиться с Graphana, это за рамками статьи, т.к. относится не только к JavaScript, но очень хорошо коррелирует с нашей темой и позволяет отображать на графиках текущее состояние приложений, слать уведомления в чат об ошибках и не только.

Ошибки зависимостей

Очень частым явлением среди разработчиков является работа со сторонними пакетами, в которых тоже могут встречаться ошибки.

Тут нет выработанного универсального решения для отлова или же игнорирования, т.к. многое зависит непосредственно от сборки самого пакета. Какие советы тут можно дать? Их немного:

  • Самым важным в логировании исключений являются уровни ошибок. Вы можете задавать их посредством встроенного console (log, warn, error, info), либо в сторонних библиотеках (см. выше log4js). Здесь решением проблемы является максимальное разделение ошибок вашего приложения и стороннего, но не переборщите, ведь могут быть действительно важные исключения.
  • Разделяйте ваши сборки на production/development/test и используйте source-map во время разработки либо пре-релиза, это позволит вам получать более детальную информацию в бою о том, что пошло не так с информативным стеком ошибки.
  • Другим способом в перехвате ошибок зависимостей является реальное устранение проблемы, например, посредством Pull Request. Для ленивых можно использовать Fork с фиксом, но тогда его нужно поддерживать, а некоторые проекты не всегда позволяют это делать.
  • Ну, и самым изощренным и неочевидным является использование соответствующих надстроек для babel. Транспайлинг посредством babel работает через AST, который в первом приближении разбирает весь код JavaScript на дерево с вершинами. Есть специальные плагины, которые делают необходимые обертки для удобства разработчиков, по типу полифиллов, перегрузок, а также оборачиванию в специальные конструкции. Оборачивать можно, как вы догадались, и обработку ошибок, но данное решение должно иметь острую необходимость, просто имейте это в виду.

Заключение

Выше были рассмотрены вполне стандартные методы обработки ошибок, а также продемонстрированы примеры техник с кодом для популярных пакетов. Дополнением выступают инструменты по автоматизации сбора и обработки ошибок. Подробную информацию читайте по ссылкам.

Комбинируйте несколько методов и доверяйте проектам с хорошей репутацией. Обязательно логируйте во время разработки, брейкпоинты только помогают понять проблему в конкретном случае, но не являются лекарством.

В предыдущих уроках мы с вами изучили два
места, в которых JavaScript выбрасывает исключение
в случае каких-то проблем.

В вашем проекте, однако, могут быть и другие
ситуации, которые для вас являются исключительными,
а для JavaScript — нет. В таком случае мы
можете создавать и выбрасывать свои, пользовательские
исключения.

Давайте разберем синтаксис, необходимый для
этого. Для начала исключение нужно создать
с помощью команды new Error, параметром
передав текст исключения:

new Error('текст исключения');

Затем это исключение нужно выбросить с помощью
команды throw:

throw new Error('текст исключения');

Выбрасывание исключение заставляет JavaScript
считать, что случилась исключительная ситуация.
Это значит, что такое исключение можно отловить
с помощью конструкции try-catch и
обработать нужным образом.

Давайте посмотрим на примере, как этим пользоваться.
Пусть у нас есть функция, которая делит одно
число на другое:

function div(a, b) {
return a / b;
}

Давайте будем считать, что деление на ноль
запрещено и при попытке сделать это должна
возникать исключительная ситуация. Для этого
будем проверять в функции, нет ли попытки
делить на 0. Если нет — будем делить,
а если нет — будем бросать исключение:

function div(a, b) {
if (b !== 0) {
return a / b;
} else {
throw new Error('ошибка деления на ноль');
}
}

Давайте для начала просто попробуем поделить
на 0, не перехватывая исключение:

alert( div(3, 0) );

В этом случае выполнение скрипта прервется
и в консоли появится ошибка с текстом 'ошибка
деления на ноль'
(проверьте). Давайте
теперь будем перехватывать нашу ошибку и
как-то ее обрабатывать:

try {
alert( div(3, 0) );
} catch (error) {
alert('вы пытаетесь делить на 0, что запрещено');
}

В JavaScript попытка извлечь корень из отрицательного
числа не приводит к выбрасыванию исключения:

let result = Math.sqrt(-1);
console.log(result); // выведет NaN

Напишите свою функцию, которая будет извлекать
корень из числа и при этом выбрасывать исключение,
если корень извлекается из отрицательного числа.

Типы исключений

Давайте выбросим свое исключение и посмотрим,
как будет вести себя объект с ошибкой в этом
случае:

try {
throw new Error('текст исключения');
} catch (error) {
console.log(error.name); // 'Error'
console.log(error.message); // 'текст исключения'
}

Как вы видите, тип нашего исключения — 'Error'.
Такой же тип будет у любых исключений, выброшенных
подобным образом. Это, однако, не всегда
будет удобно, так как, если у нас могут быть
несколько исключений, мы не сможем их отличить
друг от друга.

В JavaScript предусмотрено решение этой проблемы:
можно выбрасывать исключения не только типа
Error, но и любого встроенного в JavaScript
типа ошибки, например, TypeError,
SyntaxError, RangeError.

Давайте для примера выбросим исключение типа
SyntaxError:

try {
throw new SyntaxError('текст исключения');
} catch (error) {
console.log(error.name); // 'SyntaxError'
console.log(error.message); // 'текст исключения'
}

Выбросите исключение с типом TypeError.

Выбросите исключение с типом SyntaxError
и RangeError. Поймайте эти исключения
с помощью одного блока try. В блоке catch
выведите разные сообщения об ошибке для исключений
разных типов.

Свои типы исключений

Количество встроенных в JavaScript исключений
не такое большое и они чаще всего не могут
удовлетворить все наши потребности в разных
типах исключений. Поэтому в JavaScript встроена
возможность создавать исключения со своим типом.

Существуют разные способы сделать это. Самый
простой — в throw передать объект
с ключами name и message:

try {
throw {name: 'MyError', message: 'текст исключения'};
} catch (error) {
console.log(error.name); // 'MyError'
console.log(error.message); // 'текст исключения'
}

Выше я мы сделали функцию, выбрасывающую
исключение при делении на ноль:

function div(a, b) {
if (b !== 0) {
return a / b;
} else {
throw new Error('ошибка деления на ноль');
}
}

Переделайте эту функцию так, чтобы она выбрасывала
исключение с каким-нибудь придуманными нами
типом, например, DivisionByZeroError.

Выше вы делали функцию, выбрасывающую исключение
при попытке извлечь корень из отрицательного
числа. Переделайте вашу функцию так, чтобы
она выбрасывала исключение с придуманным
вами типом. Хорошо подумайте над названием
исключения, чтобы это название было удачным.

Пример применения

Пусть при загрузке страницы сервер создает
HTML код, в котором хранится название, цена
и количество купленного продукта:

<div id="product" data-product="яблоко" data-price="1000" data-amount="5"></div>

Давайте сделаем функцию, которая будет принимать
ссылку на элемент с продуктом и находить
полную стоимость товара (цену умножать на
количество):

function getCost(elem) {
return elem.dataset.price * elem.dataset.amount;
}

Найдем стоимость нашего продукта:

let product = document.querySelector('#product');
let cost = getCost(product);

alert(cost);

Предположим теперь следующую ситуацию: из-за
какого-то сбоя на сервере он прислал нам
товар, в котором отсутствует цена или количество
(или оба сразу), например, вот так:

<div id="product" data-product="яблоко" data-price="1000"></div>

Если теперь попробовать посчитать стоимость
товара, то результате на экран выведется
NaN. Согласитесь, не очень информативно.

Получается, нам нужно как-то обезопасится
от того, что будут отсутствовать нужные нам
атрибуты. Это можно сделать двумя путями.
Первый путь — это сказать, что это нормальное
поведение и просто поверять ифами наличие
нужных нам атрибутов:

function getCost(elem) {
if (elem.dataset.price !== undefined && elem.dataset.amount !== undefined) {
return elem.dataset.price * elem.dataset.amount;
} else {
return 0; // вернем что-нибудь, например, 0 или null или false
}
}

Второй вариант — это сказать, что отсутствие
атрибута data-price или data-amount
— исключительная ситуация. В этом случае
мы будем выбрасывать исключение:

function getCost(elem) {
if (elem.dataset.price !== undefined && elem.dataset.amount !== undefined) {
return elem.dataset.price * elem.dataset.amount;
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
}

Какой из двух вариантов здесь уместнее применить
— это выбор программиста. Он может считать
проблему нормальной работой скрипта или исключительной
ситуацией.

Пусть мы решили, что ситуация исключительная.
Тогда код получения стоимости товара будет
выглядеть вот так:

let product = document.querySelector('#product');

try {
let cost = getCost(product);
alert(cost);
} catch (error) {
// как-то реагируем на исключение
}

Переделайте мой код так, чтобы функция getCost
выбрасывала два типа исключений: если отсутствует
цена и если отсутствует количество. Хорошо
подумайте над названиями этих исключений.
В блоке catch выведите разные сообщения об
ошибке для исключений разных типов.

Еще пример применения

Пусть к нам откуда-то из внешнего мира приходит
JSON с продуктом:

let json = '{"product": "яблоко", "price": 1000, "amount": 5}';
let product = JSON.parse(json);
alert(product.price * product.amount);

Вы уже знаете, что метод JSON.parse будет
выбрасывать исключение, если JSON некорректный.
Давайте поймаем это исключение:

try {
let json = '{"product": "яблоко", "price": 1000, "amount": 5}';
let product = JSON.parse(json);
alert(product.price * product.amount);
} catch (error) {
// как-то реагируем на исключение
}

Однако, может быть такое, что сам по себе
JSON корректный, но не содержит нужных нам
полей, например, нет поля с ценой:

let json = '{"product": "яблоко", "amount": 5}'; // нет цены

Давайте скажем, что это тоже исключительная
ситуация и будем в таком случае выбрасывать
свое пользовательское исключение:

try {
let json = '{"product": "яблоко", "amount": 5}';
let product = JSON.parse(json);

if (product.price !== undefined && product.amount !== undefined) {
alert(product.price * product.amount);
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
} catch (error) {
// как-то реагируем на исключение
}

Теперь блок catch будет получать два типа
исключений: либо JSON вообще некорректен,
и тогда будет исключение типа SyntaxError,
либо JSON корректен, но не содержит нужных
нам полей, и тогда будет исключение типа
ProductCostError.

Давайте в блоке catch будем отлавливать эти
типы исключений:

try {
let json = '{"product": "яблоко", "amount": 5}';
let product = JSON.parse(json);

if (product.price !== undefined && product.amount !== undefined) {
alert(product.price * product.amount);
} else {
throw {
name: 'ProductCostError',
message: 'отсутствует цена или количество у продукта'
};
}
} catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
}
}

Пусть к вам приходит JSON вот такого вида:

let json = `[
{
"name": "user1",
"age": 25,
"salary": 1000
},
{
"name": "user2",
"age": 26,
"salary": 2000
},
{
"name": "user3",
"age": 27,
"salary": 3000
}
]`;

Проверьте этот JSON на общую корректность
при разборе, а после разбора проверьте, что
в результате получается массив, а не что-то
другое. Если в результате получается не массив
— выбросите исключение.

Проброс исключений

Рассмотрим блок catch задачи о JSON продукта:

catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
}
}

Как вы видите, мы ловим два запланированных
нами исключения и как-то реагируем на это.
Но что будет, если возникнет непредусмотренное
нами исключение другого типа? В этом случае
оно тоже попадет в блок catch, но
никакой реакции на это не последует, так
как исключение с другим типом просто не попадет
ни в один из наших ифов.

Когда я говорю, что не будет никакой реакции,
то имею ввиду, что реально никакой: даже
не будет вываливания ошибки в консоль. Наш
код просто молча не будет работать.

Поэтому существует следующее правило: ваш
код должен ловить только те исключения, с
которыми знает, как справится. Если исключение
не известное, то его нужно пробросить
дальше с помощью throw. В этом случае
его выше поймает кто-то более осведомленный
либо исключение вывалится ошибкой в консоль.

Давайте поправим наш код:

catch (error) {
if (error.name == 'SyntaxError') {
alert('Некорректный JSON продукта');
} else if (error.name == 'ProductCostError') {
alert('У продукта отсутствует цена или количество');
} else {
throw error; // пробрасываем исключение далее
}
}

Дан следующий код:

try {
let arr = JSON.parse(json);

for (let i = 0; i < arr.length; i++) {
localStorage.setItem(i, arr[i]);
}
} catch (error) {
if (error.name == 'QuotaExceededError') {
alert('закончилось место в хранилище');
}

if (error.name == 'SyntaxError') {
alert('некорректный JSON');
}
}

Что не так с этим кодом? Исправьте его на
более удачный.

Существует ряд причин, по которым код JavaScript может вызывать ошибки, например:

  • Проблема с сетевым подключением;
  • Пользователь мог ввести неверное значение в поля формы;
  • Ссылка на объекты или функции, которые не существуют;
  • Неправильные данные отправляются или принимаются с веб-сервера;
  • Служба, к которой приложение должно получить доступ, может быть временно недоступна.

Эти типы ошибок известны как ошибки времени выполнения (runtime errors), поскольку они возникают во время выполнения скрипта. Профессиональное приложение должно иметь возможность корректно обрабатывать такие ошибки во время выполнения. Обычно это означает понятное информирование пользователя о возникшей проблеме.

Оператор try…catch

JavaScript предоставляет оператор try-catch, чтобы перехватывать ошибки времени выполнения и корректно их обработать.

Любой код, который может вызвать ошибку, должен быть помещен в блок оператора try, а код для обработки ошибки помещен в блок catch, как показано здесь:

try {
    // Код, который может вызвать ошибку
} catch(error) {
    // Действие, которое нужно выполнить при возникновении ошибки
}

Если ошибка возникает в любой точке блока try, выполнение кода немедленно переносится из блока try в блок catch. Если в блоке try ошибки не возникает, блок catch будет проигнорирован, и программа продолжит выполнение после оператора try-catch.

Следующий пример демонстрирует, как работает оператор try-catch:

try {
    var greet = "Hi, there!";
    document.write(greet);
    
    // Попытка получить доступ к несуществующей переменной
    document.write(welcome);
    
    // Если произошла ошибка, следующая строка не будет выполнена
    alert("All statements are executed successfully.");
} catch(error) {
    // Обработка ошибки
  alert("Caught error: " + error.message);
}
 
// Продолжаем исполнение кода
document.write("<p>Hello World!</p>");

Приведенный выше скрипт генерирует ошибку, которая отображается в диалоговом окне с предупреждением, а не выводится в консоль браузера. Кроме того, программа не остановилась внезапно, даже если произошла ошибка.

Также обратите внимание, что за ключевым словом catch указывается идентификатор в скобках. Этот идентификатор действует как параметр функции. При возникновении ошибки интерпретатор JavaScript генерирует объект, содержащий сведения о нем. Этот объект ошибки затем передается в качестве аргумента для обработки.

Оператор try-catch является механизмом обработки исключений. Исключением является сигнал, который указывает, что во время выполнения программы возникли какие-то исключительные условия или ошибки. Термины «исключение» и «ошибка» часто используются взаимозаменяемо.

Оператор try…catch…finally

Оператор try-catch также может содержать предложение finally. Код внутри блока finally всегда будет выполняться независимо от того, произошла ошибка в блоке try или нет.

В следующем примере всегда отображается общее время, затраченное на выполнение кода.

// Присвоение значения, возвращаемого диалоговым окном
var num = prompt("Enter a positive integer between 0 to 100");

// Запоминание времени начала исполнения
var start = Date.now();

try {
    if(num > 0 && num <= 100) {
        alert(Math.pow(num, num)); // the base to the exponent power
    } else {
        throw new Error("An invalid value is entered!");
    }
} catch(e) {
    alert(e.message);
} finally {
    // Отображение времени, необходимого для выполнения кода
    alert("Execution took: " + (Date.now() - start) + "ms");
}

Вызов ошибок с помощью оператора throw

До сих пор мы видели ошибки, которые автоматически генерируются парсером JavaScript. Тем не менее, также можно вызвать ошибку вручную с помощью оператора throw.

Общий синтаксис оператора throw: throw expression;

Выражение expression может быть объектом или значением любого типа данных. Однако лучше использовать объекты, желательно со свойствами name и message. Встроенный в JavaScript конструктор Error() предоставляет удобный способ создания объекта ошибки. Давайте посмотрим на некоторые примеры:

throw 123;
throw "Missing values!";
throw true;
throw { name: "InvalidParameter", message: "Parameter is not a number!" };
throw new Error("Something went wrong!");

Если вы используете встроенные в JavaScript функции конструктора ошибок (например, Error(), TypeError() и т. д.) для создания объектов ошибок, тогда свойство name совпадает с именем конструктора, а message равно аргументу функции конструктора.

Теперь мы собираемся создать функцию squareRoot(), чтобы найти квадратный корень числа. Это можно сделать просто с помощью встроенной в JavaScript функции Math.sqrt(), но проблема здесь в том, что она возвращает NaN для отрицательных чисел, не давая никаких подсказок о том, что пошло не так.

Мы собираемся исправить эту проблему, показывая пользователю ошибку, если указано отрицательное число.

function squareRoot(number) {
    // Выдает ошибку, если число отрицательное
    if(number < 0) {
        throw new Error("Sorry, can't calculate square root of a negative number.");
    } else {
        return Math.sqrt(number);
    }
}
    
try {
    squareRoot(16);
    squareRoot(625);
    squareRoot(-9);
    squareRoot(100);
    
    // Если выдается ошибка, следующая строка не будет выполнена
    alert("All calculations are performed successfully.");
} catch(e) {
    // Обработка ошибки
    alert(e.message);
}

Теоретически можно вычислить квадратный корень из отрицательного числа, используя мнимое число i, где i2 = -1. Следовательно, квадратный корень из -4 равен 2i, квадратный корень из -9 равен 3i и так далее. Но мнимые числа не поддерживаются в JavaScript.

Типы ошибок

Объект Error является базовым типом всех ошибок и имеет два основных свойства: name, указывающее тип ошибки и свойство message, которое содержит сообщение, описывающее ошибку более подробно. Любая выданная ошибка будет экземпляром объекта Error.

Существует несколько различных типов ошибок, которые могут возникнуть во время выполнения программы JavaScript, например RangeError, ReferenceError, SyntaxError, TypeError, и URIError.

В следующем разделе описывается каждый из этих типов ошибок более подробно:

RangeError

RangeError генерируется, когда вы используете число, выходящее за пределы допустимых значений. Например, создание массива с отрицательной длиной вызовет RangeError.

var num = 12.735;
num.toFixed(200); // выдает ошибку диапазона (допустимый диапазон от 0 до 100)

var array = new Array(-1); // выдает ошибку диапазона

ReferenceError

Ошибка ReferenceError обычно выдается, когда вы пытаетесь сослаться на переменную или объект, которые не существуют, или получить к ним доступ. В следующем примере показано, как происходит ошибка ReferenceError.

var firstName = "Harry";
console.log(firstname); // выдает ошибку ссылки (имена переменных чувствительны к регистру)

undefinedObj.getValues(); // выдает ошибку ссылки

nonexistentArray.length; // выдает ошибку ссылки

SyntaxError

SyntaxError генерируется, если в вашем коде JavaScript есть какие-либо синтаксические проблемы. Например, если закрывающая скобка отсутствует, циклы не структурированы должным образом и т. д.

var array = ["a", "b", "c"];
document.write(array.slice(2); // выдает синтаксическую ошибку (отсутствует скобка)

alert("Hello World!'); // выдает синтаксическую ошибку (несоответствие кавычек)

TypeError

Ошибка TypeError возникает, когда значение не относится к ожидаемому типу. Например, вызов метода строки для числа, вызов метода массива для строки и т. д.

var num = 123;
num.toLowerCase(); /* выдает ошибку (поскольку toLowerCase() является строковым методом, число не может быть преобразовано в нижний регистр) */

var greet = "Hello World!"
greet.join() // выдает ошибку (так как join() является методом массива)

URIError

URIError генерируется, когда вы указали недопустимый URI (расшифровывается как Uniform Resource Identifier) для функций, связанных с URI, таких как encodeURI() или decodeURI(), как показано здесь:

var a = "%E6%A2%B";
decodeURI(a);  // выдает ошибку URI

var b = "uD800";
encodeURI(b);   // выдает ошибку URI

Существует еще один тип ошибки EvalError, который генерируется при возникновении ошибки во время выполнения кода с помощью функции eval(). Хотя эта ошибка больше не генерируется JavaScript, этот объект все еще остается для обратной совместимости.

Конкретный тип ошибки также может быть выдан вручную с использованием соответствующего конструктора и оператора throw. Например, чтобы сгенерировать ошибку TypeError, вы можете использовать конструктор TypeError(), например:

var num = prompt("Please enter a number");

try {
    if(num != "" && num !== null && isFinite(+num)) {
        alert(Math.exp(num));
    } else {
        throw new TypeError("You have not entered a number.");
    }
} catch(e) {
    alert(e.name);
    alert(e.message);
    alert(e.stack); // нестандартное свойство
}

Объект Error также поддерживает некоторые нестандартные свойства. Одним из наиболее широко используемых таких свойств является: stack trace, который возвращает трассировку стека для этой ошибки. Вы можете использовать его в целях отладки, но не используйте его на рабочих сайтах.

Понравилась статья? Поделить с друзьями:
  • Return outside function python ошибка
  • Return code is not 0 как исправить
  • Return code 1612 hp ошибка
  • Retry was not successful ошибка
  • Retry timeout exceeded ошибка