※ 책 [혼자 공부하는 자바스크립트]를 공부하며 기록, 정리해 놓은 노트이다.
목차
구문 오류와 예외
구문 오류, 예외(런타임 오류), 예외 처리, try cath 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이 잘 담겼다. 정확히 이런 원리(변수 상자 어쩌고)인지는 몰라도 대강은 맞는 소리라는 얘기.
- 아! 혹시 const h1 줄이 실행은 됐지만 그 다음 줄에서 에러가 발생하면서 addEventListener() 메소드 자체가 끝까지 실행되지 못했기 때문에 그런걸까?! 그 안에서 생긴 변수 상자를 저장하지 않고 갖다 버려버린 뭐 그런건가.
해결: 조건문으로 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 구문이 있고 없고의 차이:
- 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 구문이 실행되었다!
- 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 구문
요약:
예외 객체 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를 이용한 구체적 예외 던짐
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 구문입니다
해석:
- 보따리 함수 내부의 콜백에서 예외가 발생해도, 바깥의 그냥 try직속 블록 안에서 예외가 발생해도 catch 구문이 받아준다. ⇒ throw ‘보따리 함수 바깥에서~’를 주석 풀고 실행하면 이 에러 메세지로 바뀌어 잘 출력된다.
- 즉, try 구문 안에서 예외가 발생하면 직속 블록에서든 더 하위블록에서든, 간단 throw든 자세한 throw new Error()든, 자연 발생이든 강제 발생이든 다 catch가 받는다~ 이 말!
- 간단 throw로 던진 에러는 .name과 .message 속성 모두 undefined가 된다.
- 구체적 throw new Error()로 던지면 .name은 “Error”, .message는 입력한 메세지가 된다.
- 에러 이름을 정해주는 방법은 아직 못 찾았다.
참고:
[책] 혼자 공부하는 자바스크립트 - 윤인성
Uploaded by N2T