깊은바다거북
개발 공부 기록
깊은바다거북
전체 방문자
오늘
어제
  • 분류 전체보기 (219)
    • JAVA (9)
    • JavaScript (15)
    • 스파르타코딩클럽 (11)
      • [내일배움단] 웹개발 종합반 개발일지 (5)
      • [내일배움캠프] 프로젝트와 트러블 슈팅 (6)
    • SQL | NoSQL (4)
    • CS 등등 (0)
    • TIL | WIL (173)
    • 기타 에러 해결 (3)
    • 내 살 길 궁리 (4)

인기 글

최근 글

최근 댓글

태그

  • Linked List
  • tree
  • 팀 프로젝트
  • 코딩테스트 연습문제
  • 프로그래머스
  • 시간 복잡도
  • DFS(깊이우선탐색)
  • Preorder Traversal(전위 순회)
  • 재귀 함수
  • 자료 구조
  • 혼자 공부하는 자바스크립트
  • TypeScript
  • Binary Tree(이진 트리)
  • Backtracking(백트래킹)
  • BST(이진 탐색 트리)
  • Trie
  • 01. 미니 프로젝트
  • Leetcode
  • 최소 힙(Min Heap)
  • 트러블 슈팅 Troubleshooting
  • TIT (Today I Troubleshot)
  • leetcode-cli
  • 점화식(Recurrence Relation)
  • BFS(너비우선탐색)
  • 자바스크립트 기초 문법
  • 최대 힙(Max Heap)
  • Til
  • POST / GET 요청
  • 자잘한 에러 해결
  • Inorder Traversal(중위 순회)
hELLO · Designed By 정상우.
깊은바다거북

개발 공부 기록

JavaScript

[책] CH8 예외 처리

2022. 11. 13. 09:00

※ 책 [혼자 공부하는 자바스크립트]를 공부하며 기록, 정리해 놓은 노트이다.


  • 목차
    구문 오류와 예외
    요약:
    오류의 종류
    1. 구문 오류 syntax error
    2. 예외 exception (런타임 오류 runtime error)
    예외 처리 exception handling
    1. 기본 예외 처리
    2. 고급 예외 처리
    예외 처리 고급
    요약:
    예외 객체 exception object
    예외 객체의 속성
    throw 구문
    “자바스크립트는 예외를 잘 발생시키지 않아요. 아주 얌전하죠”
    try 구문 안에서 throw로 던진 에러도 catch가 받을까?

구문 오류와 예외

구문 오류, 예외(런타임 오류), 예외 처리, try cath finally 구문

요약:

☝🏿
오류에는 구문 오류와 런타임 오류(=예외) 두 가지가 있고
☝🏿
그 중 런타임 오류를 프로그래밍으로 처리해야 하는데(=예외 처리),
☝🏿
조건문으로 처리하는 기본 예외 처리나 try-catch 구문을 쓰는 고급 예외 처리 둘 중 원하는 것을 쓰면 된다.
☝🏿
finally 구문은 try-catch-finally보다 바깥의 블록이 조기 종료되는 시점에도 꿋꿋이 실행되고서야 빠져나가는 것을 허락한다.


오류의 종류

1. 구문 오류 syntax error

: 프로그램 실행 전에, 코드의 문법적인 문제로 발생하는 오류. 웹 브라우저가 오류의 위치를 사전에 정확하게 짚어서 알려준다. “Uncaught SyntaxError”

예시)

괄호를 닫지 않은 경우: Uncaught SyntaxError: missing ) after argument list

let을 le로, 예약어 오타를 낸 경우(뒤에 오는 식별자를 못알아 보겠다고 오류를 냄): Uncaught SyntaxError: Unexpected identifier ‘a’

2. 예외 exception (런타임 오류 runtime error)

: 프로그램 실행 중에 발생하는 모든 오류. SyntaxError 외의 모든 오류(TypeError, ReferenceError, RangeError).

예시)

console.log를 console.rog라고 입력한 경우(rog는 undefined인데 그걸 함수 호출 형태로 사용하니): Uncaught TypeError: console.rog is not a function

보통 “OOO.XXX()” 이런 호출이 있을 때, 일단 첫 식별자 “OOO”가 존재하지 않는 경우 “정의되지 않은 참조 변수를 불렀다”고 참조 에러(Uncaught ReferenceError: OOO is not defined)가 뜬다. 뒤에가 더 길게 이어지는 경우도 마찬가지.

이제 첫 식별자 “OOO”가 존재한다는 전제 하에,

“OOO.XXX()” 첫 식별자는 존재하고 마지막 “XXX()”가 존재하지 않는 경우에는 “OOO.XXX() 얘는 함수가 아니야”라고, 일단 OOO는 아는 채 한 번 해주고 타입 에러(Uncaught TypeError: OOO.XXX is not a function).

“OOO.XXX” undefined 반환. 에러 없음

“OOO.ㅁㅁㅁ.XXX()” 와 “OOO.ㅁㅁㅁ.XXX” “ㅁㅁㅁ”가 존재하지 않으면 둘 다 “정의되지 않은 ㅁㅁㅁ에게 속성이 있을 리 없잖아”라고 타입 에러(Uncaught TypeError: Cannot read properties of undefined (reading 'XXX')). 위에서 봤듯이 .ㅁㅁㅁ까지는 문제가 없었고, ‘XXX를 읽다가’ 에러를 내보낸 것이다.


예외 처리 exception handling

: ‘예외’가 발생했을 때 프로그램이 중단되지 않게 하는 처리이다. 구문 오류는 예외 처리로 처리할 수 없음.

1. 기본 예외 처리

: 조건문(if else)을 사용해서 예외가 발생하지 않게 만드는 것.

예시)

문제: querySelector() 메소드로 추출된 문서 객체가 없는 경우

<script>
    // 문제: querySelector()로 h1 문서 객체를 선택하려는데 body 태그 내에 h1 태그가 없다.
    document.addEventListener('DOMContentLoaded', () => {
        const h1 = document.querySelector('h1')
        h1.textContent = '안녕하세요!'
    })
</script>
<body></body>
=> Uncaught TypeError: Cannot set properties of null (setting 'textContent')
    at HTMLDocument.<anonymous>

⇒ document.querySelector(’h1’)은 null을 반환한다.

⇒ 그래서 “null의 속성은 변경할 수 없다”는 에러가 뜨는 것.

⇒ (그럼에도 불구하고) h1에는 담긴 것이 없다. h1을 콘솔에 찍어보면 null이나 undefined가 나오는 게 아니라 Uncaught ReferenceError: h1 is not defined at <anonymous> 에러가 뜸.

  • 에러가 발생한 곳이 정확히 5라인, h1.textContent = ‘안녕하세요!’ 부분이었다. 그렇다면 그 전에 const h1은 처리가 되었다는 건데(런타임 에러니까), 왜 h1에 null이 담기지 않고 아예 정의조차 되지 않았다고 나올까? 콘솔에서 저 줄 그대로 실행해보면 잘만 null이 담기는데.
    • 아! 혹시 const h1 줄이 실행은 됐지만 그 다음 줄에서 에러가 발생하면서 addEventListener() 메소드 자체가 끝까지 실행되지 못했기 때문에 그런걸까?! 그 안에서 생긴 변수 상자를 저장하지 않고 갖다 버려버린 뭐 그런건가.

      A. 예외 처리를 잘 해주니 null이 잘 담겼다. 정확히 이런 원리(변수 상자 어쩌고)인지는 몰라도 대강은 맞는 소리라는 얘기.

해결: 조건문으로 h1이 존재하는 경우에만 textContent 속성을 변경하도록 예외 처리를 한다.

<script>
    // 문제: querySelector()로 h1 문서 객체를 선택하려는데 body 태그 내에 h1 태그가 없다.
		// 해결: h1이 존재하는 경우에만 h1의 textContent 속성을 변경하도록 예외 처리.
    document.addEventListener('DOMContentLoaded', () => {
        const h1 = document.querySelector('h1') // h1 태그가 존재하지 않으면 'null'
        if (h1) {
            h1.textContent = '안녕하세요!'
        } else {
            console.log('h1 태그를 추출할 수 없습니다.')
            console.log('가설이 맞다면, 예외처리를 해서 코드 블럭 끝까지 '
            + '잘 마쳤기 때문에 그 사이에 생성된 변수 상자가 잘 저장되었습니다. '
            + '그래서 현재 h1의 값은 null이 되어야 합니다. '
            + `\n=> ${h1}`
            + `\n${h1 === null ? h1+'입니다!!! 맞았습니다!' : '가설이 틀렸네요'}`)
        }
    })
</script>
<body></body>
=> 결과:
h1 태그를 추출할 수 없습니다.
가설이 맞다면, 예외처리를 해서 코드 블럭 끝까지 잘 마쳤기 때문에 그 사이에 생성된 변수 상자가 잘 저장되었습니다. 그래서 현재 h1의 값은 null이 되어야 합니다. 
=> null
null입니다!!! 맞았습니다!

⇒ 예외 처리를 하니 (아마도 프로그램이 끝까지 실행되어)상수 h1에도 null이 잘 담겼다.

2. 고급 예외 처리

: try catch finall 구문을 사용해서 예외를 처리하는 것.

try { // 예외가 발생할 가능성이 있는 코드 } catch (exception) { // 예외가 발생했을 대 실행할 코드 } finally { // 예외가 생겨도 안 생겨도 무조건 실행할 코드 }

예시) finally 구문이 있고 없고의 차이:

  1. try catch 구문 내부에서 return 키워드를 만났을 때:
// finally 구문이 없는 경우
function test1() {
    try {
        console.log('\ntry 위치입니다')
        throw "예외 강제 발생"
    } catch (exception) {
        console.log('catch 위치입니다')
        return
    }
    console.log('try-catch 바깥 위치입니다')
}
test1()
=> 결과:
try 위치입니다
catch 위치입니다
// finally 구문이 있는 경우
function test2() {
    try {
        console.log('\ntry 위치입니다')
        throw "예외 강제 발생"
    } catch (exception) {
        console.log('catch 위치입니다')
        return
    } finally {
        console.log('finally 위치입니다')
    }
    console.log('try-catch-finally 바깥 위치입니다')
}
test2()
=> 결과:
try 위치입니다
catch 위치입니다
finally 위치입니다

⇒ return으로 함수를 벗어났는데도 불구하고 finally 구문이 실행되었다!

  1. try catch 구문 내부에서 break 또는 continue 키워드를 만날 때
// 고급 예외 처리 - continue 키워드를 만났을 때 finally가 있고 없고의 차이:
var count = 1;
while (count < 5){
    try{
        if (count == 3) continue;
    }
    finally {
        console.log("count = " + count);
        count++;
    }
}
=> 결과:
count = 1
count = 2
count = 3
count = 4

⇒ count++가 finally에 있지 않았다면 원래는 반복문 계수 변수 count가 3이 되는 시점에 멈춰서 무한 반복됐어야 했다. 그런데 finally는 정말 ‘무조건’ 실행되어야 하기 때문에 반복문을 나가기 전 들렀다가 count++라는 과정을 거쳐버렸고, 다행히 반복문이 정상적으로 끝날 수 있게 됨.

⇒ 반복문 안에서 continue나 break를 자유롭게 쓰고, 다만 반복문 계수 변수 i등이 매번 꼭 증가하게 만들고 싶을 때 finally 안에다 넣는 방식으로 처리면 되겠다.

⇒ catch와 finally 둘 중 하나는 생략될 수 있다.


자바스크립트는 아주 유연해서 예외를 잘 발생시키지 않는다. 예를 들면 배열 길이를 넘는 인덱스로 불러도 그냥 undefined를 출력하고 끝. 그래서 알아서 문제가 발생할 소지가 있는 부분에 조건문 등으로 처리를 잘 해주어야 한다.

기본 예외 처리가 약간 더 빠르지만 별 차이 없다고 한다. 편하다고 생각하는 방법을 사용하고, 어떤 예외가 발생할지 예측하기 힘든 경우에 고급 예외 처리로 처리해주면 좋다.



예외 처리 고급

예외 객체, throw 구문

요약:

☝🏿
예외 객체는 catch 구문이 잡아내고 사용자에게 던지는 바로 그 애. 기본적으로 name과 meassage 속성을 가진다.
☝🏿
예외를 강제로 발생시키는 방법에는 간단히 메시지만 던지는 “throw 메세지” 방식과 좀 더 구체적으로 에러 타입 + 메세지 + 발생 위치를 던지게 하는 “throw new Error(메세지)” 방식이 있다.


예외 객체 exception object

: 예외와 관련된 정보를 담은 객체. catch의 괄호 안에 담기는 식별자.

try { … } catch (exception) { …. }

예외 객체의 속성

모든 웹 브라우저가 공통적으로 갖는 속성은 예외 이름(name)과 예외 메세지(message)이다.

// 예외 객체 출력하기 - 이름과 메세지
try {
    const arrr = new Array(9999999999999999);  
} catch (exception) {
    console.log('\n' + exception)
    console.log(`예외 이름: ${exception.name}`) 
    console.log(`예외 메세지: ${exception.message}\n `)
}
=> 결과:
RangeError: Invalid array length
예외 이름: RangeError
예외 메세지: Invalid array length

⇒ 자바스크립트 배열이 가질 수 있는 최대 크기는 4,294,967,295라 예외가 발생됨.


throw 구문

: 예외를 강제로 발생시킬 때 사용하는 구문. 예외가 던져진 즉시 프로그램 실행이 멈춘다.

단순하게 예외를 발생시킬 땐: throw 메세지 ⇒ Uncaught 메세지

더 자세하게 예외를 발생시킬 땐: throw new Error(메세지) ⇒ Uncaught Error: 메세지 at 파일명:줄번호

// throw 구문
(function (a, b) {
    if (b === 0) {
        throw new Error('0으로는 나눌 수 없다니까요') 
        throw '0으로는 나눌 수 없습니다.'  // Uncaught 0으로는 나눌 수 없습니다.
    }
    return a / b
}) (10, 0)
=> 결과:
Uncaught Error: 0으로는 나눌 수 없다니까요
    at 파일이름:줄번호

=> 먼저 던져지는 쪽만 출력됨. 즉시 실행이 멈추기 때문에 두 번째 throw는 출력되지 않음.


“자바스크립트는 예외를 잘 발생시키지 않아요. 아주 얌전하죠”

⇒ 그래서 문제가 됨. 프로그래머가 좀 더 똑똑해져야 해서.

// 이상한 자바스크립트 코드 해석
function test(object) {
		console.log(object.a + object.b)
}
test( {} )
=> 결과:
NaN

⇒ 일반적인 프로그래밍 언어라면 1) 빈 객체({ })에 속성 a와 b가 없다는 에러와 2) 없는 것들을 더하고 있다는 에러가 뜰 것이나,

⇒ 자바스크립트에서는 존재하지 않는 속성은 undefined로, 존재하지 않는 값끼리 더하는 undefined+undefined는 NaN으로 나온다.

  • 왜 함수 실행에 { }를 넣어서 하는가..아하! 저 object는 타입 제한자도 아니고 그냥 식별자니까 실제로 인자를 ‘객체’로 만들어주는 건 전달된 { } 얘겠구나.

그래서 예외를 발생시켜서 사용자에게 함수를 잘못 사용했다는 것을 인지시켜줄 필요가 있다:

// undefined + undefined = NaN을 도출하지 말고 예외를 던지라고 고쳐준 코드:
function test(object) {
		if (object.a !== undefined && object.b !== undefined) {
				console.log(object.a + object.b)
		} else {
				throw new Error("a 속성이나 b 속성을 지정하지 않았습니다.")
		}
}
test( {} )
=> 결과:
Uncaught Error: a 속성이나 b 속성을 지정하지 않았습니다.

⇒ if else를 이용한 기본 예외 처리(?) + throw new Error를 이용한 구체적 예외 던짐

💡
⇒ 어떤 값이 undefined인지 비교할 땐 object.a !== undefined 이렇게 한다는 것을 기억하자


try 구문 안에서 throw로 던진 에러도 catch가 받을까?

아니면 try 안에서 그냥 바로 던질까?

// 콜백 함수 내부에서 예외가 발생할 경우엔? + try 구문에 throw가 있는데 catch에서 받고 있는 경우:
try {
    console.log('\ntry 구문입니다')
    const array = ['사과', '수박', '포도']
    // throw '보따리 함수 바깥에서 그냥 발생한 예외는 어떻게, 받을까요 그냥 바로 던져질까요?' // 받아지네.
    array.forEach(() => {
        throw 'forEach 내부의 콜백입니다. 예외를 강제로 발생시켰습니다. '
    })
} catch (e) {
    console.log('catch 구문입니다')
    console.log('throw로 간단 정의한 에러는 타입이 어떻게 나타날지 궁금해서 출력해보는 객체 e: \n', e)
    console.log(e.name, e.message)
} finally {
    console.log('finally 구문입니다\n ')
}
=> 결과:
try 구문입니다
catch 구문입니다
throw로 간단 정의한 에러는 타입이 어떻게 나타날지 궁금해서 출력해보는 객체 e: 
 forEach 내부의 콜백입니다. 예외를 강제로 발생시켰습니다.
undefined undefined
finally 구문입니다

해석:

  1. 보따리 함수 내부의 콜백에서 예외가 발생해도, 바깥의 그냥 try직속 블록 안에서 예외가 발생해도 catch 구문이 받아준다. ⇒ throw ‘보따리 함수 바깥에서~’를 주석 풀고 실행하면 이 에러 메세지로 바뀌어 잘 출력된다.
  1. 즉, try 구문 안에서 예외가 발생하면 직속 블록에서든 더 하위블록에서든, 간단 throw든 자세한 throw new Error()든, 자연 발생이든 강제 발생이든 다 catch가 받는다~ 이 말!
  1. 간단 throw로 던진 에러는 .name과 .message 속성 모두 undefined가 된다.
  1. 구체적 throw new Error()로 던지면 .name은 “Error”, .message는 입력한 메세지가 된다.
  1. 에러 이름을 정해주는 방법은 아직 못 찾았다.


참고:

[책] 혼자 공부하는 자바스크립트 - 윤인성


Uploaded by N2T

    'JavaScript' 카테고리의 다른 글
    • [책] CH9 클래스 part2(끝). 클래스 고급
    • [책] CH9 클래스 part1. 클래스의 기본 구성
    • [책] CH7 문서 객체 모델(DOM)
    • [책] CH6 객체 part4(끝). 다중 할당과 깊은 복사
    깊은바다거북
    깊은바다거북

    티스토리툴바