Обработка ошибок и стек вызова функций

Последнее обновление: 01.09.2023

Если внутри функции возникает ошибка, которая не обрабатывается, то интерпретатор JavaScript выходит из этой функции во внешний код в поиске обработчика ошибки. Например:

function A(){
	console.log("func A starts");
	callSomeFunc();
	console.log("func A ends");
}
console.log("program ends");

Здесь функция A вызывает не определенную функцию callSomeFunc(). Поэтому при выполнении программы при вызове функции A прерывается. Интерпретатор выходит во внешний код в поиске обработчика ошибки. Но во внешнем коде вокруг вызова функции A также не определена конструкция try..catch, поэтому выполнение всей программы аварийно завершится. Консольный вывод:

func A starts
Uncaught ReferenceError: callSomeFunc is not defined
    at A (index.html:11:2)
    at index.html:30:6

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

function A(){
	console.log("func A starts");
	callSomeFunc();
	console.log("func A ends");
}
function B(){
	console.log("func B starts");
	A();
	console.log("func B ends");
}
function C(){
	console.log("func C starts");
	B();
	console.log("func C ends");
}
C();
console.log("program ends");

Здесь вызывается функция C, которая вызывает функцию В, которая вызывает функцию А, а та - несуществующую функцию callSomeFunc. В итоге в функции А возникнет ошибка. Поскольку функция А не обработала ошибку, интерпретатор последовательно ищет обработчик ошибки в функции В, затем в функции С и в конце в глобальном контексте. Но так как нигде ошибка не обрабатывается, то после возникновения ошибки, выполнение программы завершится:

func C starts
func B starts
func A starts
Uncaught ReferenceError: callSomeFunc is not defined
    at A (index.html:11:2)
    at B (index.html:16:2)
    at C (index.html:27:2)
    at index.html:31:1

Теперь определим в одной из функций обработчик ошибки, например, в функции С:

function A(){
	console.log("func A starts");
	callSomeFunc();
	console.log("func A ends");
}
function B(){
	console.log("func B starts");
	A();
	console.log("func B ends");
}
function C(){
	console.log("func C starts");
	try{
		B();
	}
	catch{
		console.log("Error occured");
	}
	console.log("func C ends");
}

C();
console.log("program ends");

Опять же при возникновении ошибки интепретатор сначала ищет обработчик в функции А. Поскольку в ошибка не обрабатывается, интепретатор смотрит в окружающем коде в функции В, в ней тоже нет. Тогда поиск идет в функции С - в ней ошибка обрабатывается, и программа продолжает свою работу:

func C starts
func B starts
func A starts
Error occured
func C ends
program ends

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

Проброс ошибки вверх по стеку вызова функций

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

// класс условной базы данных
class Database{
    constructor(){
        this.data = ["Tom", "Sam", "Bob"];
    }
    // получение данных
    getItem(index){ 
        if(index > 0 && index < this.data.length)
            return this.data[index];
        else    // если некорректный индекс - генерируем ошибку
            throw new RangeError("Invalid index");
    }
    // открытие бд
    open(){
        console.log("Database has opened");
    }
    // закрытие бд
    close(){
        console.log("Database has closed");
    }
}
// функция-обертка для получения объекта из базы данных по индексу
function get(index) {
  
    const db = new Database();
    db.open();  // условно открываем бд
    try {
        return db.getItem(index);   // возвращаем полученный элемент
    } catch(err) {
        console.error(err); // если произошла ошибка обрабатываем ее 
    }
    db.close(); // условно закрываем бд
}
// вывод результата
function printResult(){
    const item = get(5);    // пытаемся получить элемент с индексом 5
	console.log("Got from database:",item); // выводим полученный элемент на консоль
}
printResult();

Здесь определен условный класс базы данных Database. Для взаимодействия с данными в нем определены три функции. Функции условного открытия и закрытия базы данных - функции open и close соответственно и функция getItem, которая возвращает элемент по определенному индексу из массива data. Однако если переданный индекс некорректен - меньше 0 или больше допустимого индекса, то генерируем ошибку RangeError.

Стоит отметить отметить, что это не единственный подход к реализации подобного функционала. Частно применяется другой подход, когда, если некорректный индекс/id, то функция возвращает null, что в определенных ситуациях может быть более предпочтительным. Но в данном случае для демонстрации остановимся на генерации исключения, если передан некорректный индекс.

Условимся, что, чтобы взаимодействовать с базой данных, нам надо ее вначале условно "открыть" методом open, а после завершения работы с ней "закрыть" с помощью метода close - довольно распространенный подход при работе с базами данных. Но чтобы абстрагироваться от всех этих деталей определяем дополнительную функцию get, которая принимает id и обращается к базе данных для получения элемента по id. И поскольку при обращении к методу getItem может произойти ошибка, то обрабатываем ее в конструкции try..catch

try {
    return db.getItem(index);   // возвращаем полученный элемент
} catch(err) {
    console.error(err); // если произошла ошибка обрабатываем ее 
}

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

function printResult(){
    const item = get(5);    // пытаемся получить элемент с индексом 5
	console.log("Got from database:",item); // выводим полученный элемент на консоль
}

Если мы посмотрим на вывод программы:

Database has opened

RangeError: Invalid index at Database.getItem (index.html:19:19) at get (index.html:36:19) at printResult (index.html:44:18) at index.html:47:1 get @ index.html:38 printResult @ index.html:44 (anonymous) @ index.html:47

Database has closed Got from database: undefined

то увидим, что при передаче некорректного индекса, с одной стороны, ошибка обрабатывается, но с другой стороны, в функции printResult мы получаем неопределенный результат (значение undefined) и вообще не в курсе, что произошло - это можно узнать только по консольному выводу во время выполнения. Но функция get необязательно должна выводить ошибку на консоль, ошибка может обрабатываться каким-то другим образом. Соответственно возникает необходимость донести информацию об ошибке до более верхних уровней стека вызова функций (до функции printResult).

Для этого изменим код, выполнив проброс ошибки вверх по стеку вызова функций:

class Database{
    constructor(){
        this.data = ["Tom", "Sam", "Bob"];
    }
    getItem(index){ 
        if(index > 0 && index < this.data.length)
            return this.data[index];
        else
            throw new RangeError("Invalid index");
    }
    open(){
        console.log("Database has opened");
    }
    close(){
        console.log("Database has closed");
    }
}
function get(index) {
  
    const db = new Database();
    db.open();
    try {
        return db.getItem(index);
    } catch(err) {
        console.error(err);
        throw new Error(err.message);   // снова генерируем ту же ошибку
    }
    finally{
        db.close();
    }
}
function printResult(){
    try{
        const item = get(5);
	    console.log("Got from database:",item);
    }
    catch(err){
        console.log(err);   // обрабатываем ошибку из функции get
    }
}
printResult();

Теперь в функции get после обработки ошибки повторно генерируем ошибку с помощью оператора throw:

try {
    return db.getItem(index);
} catch(err) {
    console.error(err);
    throw new Error(err.message);
}
finally{
    db.close();
}

Также стоит отметить, что вызов db.close(), который условно закрывает базу данных, помещается в блок finally. Это гарантирует, что даже при генерации ошибки эта операция все равно будет выполнена.

Таким образом, если при вызове db.getItem произошла ошибка, то при вызове функции get тоже произойдет ошибка, соотвественно в функции printResult мы можем обработать эту ошибку:

function printResult(){
    try{
        const item = get(5);
	    console.log("Got from database:",item);
    }
    catch(err){
        console.log(err);
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850