LHJ

I'm a FE developer.

11.4 예외 처리와 호출 스택

18 May 2020 » js_lj

11.4 예외 처리와 호출 스택

프로그램이 함수를 호출하고, 그 함수는 다른 함수를 호출하고, 호출된 함수는 또 다른 함수를 호출하는 일이 반복됩니다.
자바스크립트 인터프리터는 이런 과정을 모두 추적하고 있어야 합니다.
함수 a가 함수 b를 호출하고 함수 b는 함수 c를 호출한다면, 함수 c가 실행을 마칠 때 실행 흐름은 함수 b로 돌아갑니다.
그리고 b가 실행을 마칠 때 실행 흐름은 함수 a로 돌아갑니다.
바꿔 말해, c가 실행 중일 때는 a와 b는 완료될 수 없습니다.
이렇게 완료되지 않은 함수가 쌓이는 것을 호출 스택(call stack)이라 부릅니다.

c에서 에러가 일어난다면 a와 b는 어떻게 될까요?
b는 c가 반환하는 값을 사용해야 할 수도 있으므로, b에서도 에러가 일어납니다.
따라서 a에서도 에러가 일어납니다.
요약하자면, 에러는 캐치될 때까지 호출 스택을 따라 올라갑니다.

에러는 호출 스택 어디에서든 캐치할 수 있습니다.
어디에서 이 에러를 캐치하지 않으면 자바스크립트 인터프리터는 프로그램을 멈춥니다.
이런 것을 처리하지 않은(unhandled) 예외, 캐치하지 않은(uncaught) 예외라고 부르며 프로그램이 충돌하는 원인이 됩니다.
에러가 일어날 수 있는 곳은 정말 다양하므로 가능한 에러를 모두 캐치하기는 정말 어렵습니다.

에러를 캐치하면 호출 스택에서 문제 해결에 유용한 정보를 얻을 수 있습니다.
예를 들어 함수 a가 함수 b를 호출하고 b가 호출한 c에서 에러가 일어났다면, 호출 스택은 c에서 일어난 에러를 보고하는 데 그치지 않고 b가 c를 호출했으며 b는 a에서 호출했다는 것도 함께 알려줍니다.
프로그램 여기저기에서 함수 c를 호출할 수 있으므로 이런 정보는 디버그에 유용하게 쓸 수 있습니다.

대부분의 자바스크립트 환경에서 Error 인스턴스에는 스택을 문자열로 표현한 stack 프로퍼티가 있습니다.
이 기능은 자바스크립트 표준은 아니지만 대부분의 환경에서 지원합니다.
예외 처리에 대해 여러 가지를 알게 됐으니 예제를 살펴봅시다.

function a() {
    console.log('a: calling b');
    b();
    console.log('a: done');
}
function b() {
    console.log('b: calling c');
    c();
    console.log('b: done');
}
function c() {
    console.log('c: throwing error');
    throw new Error('c error');
    console.log('c: done');
}
function d() {
    console.log('d: calling c');
    c();
    console.log('d: done');
}

try {
    a();
} catch (err) {
    console.log(err.stack);
}

try {
    d();
} catch (err) {
    console.log(err.stack);
}

이 예제를 파이어폭스에서 실행하면 콘솔에 다음과 같은 결과가 출력됩니다.

a: calling b
b: calling c
c: throwing error
c@debugger eval code:13:1
b@debugger eval code:8:4
a@debugger eval code:3:4
@debugger eval code:23:4

d: calling c
c: throwing error
c@debugger eval code:13:1
d@debugger eval code:18:4
@debugger eval code:29:4

@ 기호가 있는 행은 스택 추적이며 ‘가장 깊은’ 함수(c)에서 시작하고 함수가 남지 않았을 때(브라우저 자체) 끝납니다.
스택 추적 두 가지가 나타나 있습니다.
하나는 a가 b를, b가 c를 호출했음을 보여주고 다른 하나는 d에서 c를 호출했음을 보여줍니다.