page owner's profile image
minsug
try finally문의 작동 원리를 ECMAScript로 알아보자ECMAScript 시리즈 2
2024년 5월 26일
7분

try finally가 이상하다

function bar(x, y) {
  const sum = x + y;
 
  try {
    if (sum === 3) {
      return "sum is 3";
    }
  } finally {
      return "sum is not 3";
  }
}
 
const barResult = bar(1, 2);
 
console.log(barResult)

위 코드를 실행시켰을 때 몇 번이 출력될 것 같은가?

  1. sum is 3
  2. sum is not 3
  3. sun is 3 sum is not 3
  4. sum is not 3 sum is 3

답은 뒤에서 말씀드리겠다

에러 핸들링에 사용되는 try-catch-finally 문에서 finally 문이 어떠한 상황에도 실행되는 것은 익히 알려져 있다. 어떤 원리로 작동되는 것일까?

실제 상황에서는 catch 문도 사용해야하지만 이번 글에서 살펴볼 작동 원리에서 catch의 유뮤가 차이를 발생시키지 않아서 생략했습니다.

finally에게 우선권이 있다

finally 문의 return이 최종 return임을 말하고 있다.finally 문의 return이 최종 return임을 말하고 있다.

MDN에서 finally 문의 return이 try-catch의 return을 덮는(mask)다고 말하고 있다.

즉, 위 코드의 답은 2번 sum is not 3이다. 그렇다면 어떻게 finally의 return은 다른 return들을 덮을 수 있을까? MDN은 이에 대해서 제어권이 finally에 우선적으로 있기 때문에 finally를 반환한다고 말한다. 명쾌한 설명이지만 아직 궁금중이 있다. 만약 try의 return에 실행문이 담겨있다면 어떻게 발생할까?

다시 말해서, try의 return은 반환만 안할뿐 실행은 될까?

위 예제 코드를 살짝만 바꿔보자, 콘솔 출력을 예측할 수 있는가?

function bar(x, y) {
  const sum = x + y;
 
  try {
    if (sum === 3) {
      return console.log(10); // return 문에 console.log룰 넣었다.
    }
  } finally {
      return "sum is not 3";
  }
}
 
const barResult = bar(1, 2);
 
console.log(barResult)

마찬가지로 답은 뒤에서 말씀드리겠다.

깊게 더 깊게

아래 사진은 ECMAScript에서 설명하는 try finally 문 작동 순서다. 마지막 예제에 대한 답뿐만 아니라 MDN은 설명하지 않은 작동 원리를 더 자세히 표현하고 있다.

이해할 수 없는 단어들로 가득한 ECMAScript.. 그래도 읽다보면 반복되는 단어들을 발견 할 수 있다.이해할 수 없는 단어들로 가득한 ECMAScript.. 그래도 읽다보면 반복되는 단어들을 발견 할 수 있다.

먼저 용어를 이해해보자.

  • Evaluation: 특정 코드 블록이나 표현식을 실행하고 결과를 산출하는 과정
  • Completion: 코드 블록이나 명령어의 실행이 어떻게 끝났는지를 설명하는 결과
  • Normal Completion: break, continue, throw, return에 의해 종료되지 않은 completion

작동 순서는 다음과 같다. try 문과 finally 문의 평가 결과를 각각 F와 B에 할당한다. 그리고 F가 만약 normal completion이라면 F를 B로 재할당한 후 F를 반환한다.

그렇다, MDN에서 말한 것처럼 제어권은 애초에 finally에 있다. 왜냐하면 return되는 것이 F이고 특정 조건을 만족해야지만 B가 F에 재할당되기 때문이다.

그럼 마지막 예제를 ECMAScript에서 설명하는 작동 순서로 해석해보자.

  1. try 문이 실행되고 결과값이 B에 할당된다. 이때 try의 return이 console.log이기 때문에 즉시 실행되며 undefined가 B에 할당된다. 즉, 답은 10과 sum is not 3 모두 다 출력된다.
  2. finally 문이 실행되고 결과값이 F에 할당된다.
    이때 finally의 return이 sum is not 3이 F에 할당된다.
  3. F가 normal completion인지 확인한다.
    return에 의해 종료되었음으로 normal completion이 아닌 return completion이다.
  4. F가 B로 재할당되지 않고 F를 반환한다.

MDN은 말하지 않는 사실이 여기에 두 가지 있다.

첫번째로 try 문의 return은 실행되지만 최종 반환값은 finally의 평가 결과에 따라 달라진다는 것.

두번째로 finally의 return 뿐만 아니라 break, continue, 그리고 throw까지 try의 return을 덮어 쓸 수 있는 조건이라는 것.

Completion은 normal, break, continue, return, throw, 그리고 abrupt로 이루어져 있다. break completion은 break에 의해 종료된 문을 뜻하며 나머지도 해당 키워드에 의해 종료된 문을 이야기한다. abrupt는 normal 를 제외한 나머지 completion을 의미한다.

암기가 아닌 이해하기

ECMAScript로 동작 원리를 찾아보는 것은 공부하는 스토리를 만들뿐만 아니라 이해를 바탕으로 하기 때문에 변칙적인 상황에서 유연하게 대처할 수 있게 한다. 시간이 많이 소요되지만 자바스크립트를 확실히 공부하는 방식이라고 생각한다. 앞으로도 단순 암기가 아닌 이해하는 공부를 하고자 한다. 이를 위해서는 “왜"라는 질문을 계속 던져야 하는 것 같다.

최근에 async와 await에 대해서 이해되지 않는 부분들이 있었는데 다음 글에서 한번 다루고 싶다.(공부 시간이 얼마나 걸리지는 모르겠지만…)