js Error

과거 자바스크립트가 아닌 다른 언어로 프로그래밍 할 때도 에러를 주로 caller에게 넘겨서 처리하곤 했었다.

자바스크립트로 프로그래밍 하다보면 에러를 발생시킨 caller를 차례대로 콘솔에 찍어준다.
이에 대한 정확한 로직이 궁금하여 공부 해보았다.

Error

Error객체는 런타임 오류가 발생했을 떄 던져진다.
Error 객체를 사용자 지정 예외의 기반 객체로 사용할 수 있다.

Error() 생성자를 통해 Error 객체를 생성할 수 있다.

Error 생성하기

throw 문을 통해 예외를 발생시킬 수 있다.

1
2
3
4
5
6
try {
throw "new Error";
} catch (e) {
console.error(e);
}
//new Error

다음과 같이 catch문을 작성하지 않아도 예외는 발생한다.

1
2
3
4
5
6
7
8
9
function sum(x, y) {
if (typeof x !== "number" || typeof y !== "number") {
throw "숫자가 아닙니다.";
}
return x + y;
}

console.log(sum(1, 2)); //3
console.log(sum("a", 2)); // Uncaught 숫자가 아닙니다.

콜스택과 에러

throw로 예외를 발생 시켰을 때는 현재 함수의 실행이 중지된다.
그 후 콜스택의 첫번째 catch 블록으로 전달된다.
만약 catch 블록이 존재하지 않을 경우 프로그램이 종료된다.

예외처리를 해주지 않았을 경우

아래의 코드는 예외처리를 해주지 않은 코드이다.
이 경우엔 콜스택의 첫 번째 요소부터 catch 블록을 탐색하고 처리될 때 까지 콜 스택을 차례대로 비우게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f2() {
console.log("f2 start");
throw "에러발생";
console.log("f2 end");
}

function f1() {
console.log("f1 start");
f2();
console.log("f1 end");
}

console.log("will:f1");
f1();
console.log("did: f1");

실행 순서는 다음과 같다.

  1. 콜스택이 비어있기 때문에 익명함수 호출
  2. will:f1
  3. 익명함수가 f1호출
  4. f1 start
  5. f1이 f2 호출
  6. f2 start
  7. Uncaught 에러발생
  8. 에러가 발생하며 처리될 떄 까지 호출스택을 비움

예외처리를 해주었을 경우

아래의 코드는 try catch 구문을 통해 예외처리를 해주었다.
때문에 아래의 부분은 파괴되지 않고 실행된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function f2() {
console.log("f2 start");
throw "에러발생";
console.log("f2 end");
}

function f1() {
console.log("f1 start");
try {
f2();
} catch (e) {
console.log(e);
}
console.log("f1 end");
}

console.log("will:f1");
f1();
console.log("did: f1");
  1. 익명함수 호출
  2. will:f1
  3. 익명함수가 f1호출
  4. f1 start
  5. f1이 f2 호출
  6. f2 start
  7. 에러발생
  8. f1 end
  9. did: f1

Error 객체

아래 코드는 Error 객체를 이용하여 예외를 발생시켰을 경우이다.
이럴 경우 에러가 발생했을 당시의 콜스택 상황을 확인할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function f2() {
console.log("f2 start");
throw new Error("에러");
console.log("f2 end");
}

function f1() {
console.log("f1 start");
f2();
console.log("f1 end");
}

console.log("will:f1");
try {
f1();
} catch (e) {
console.log(e);
}
console.log("did: f1");
  1. 익명함수 호출
  2. 익명함수가 f1호출
  3. f1 start
  4. f1이 f2 호출
  5. f2 start
  6. 에러 + 콜스택 정보( f2에서의 콜스택 상황을 반환함)
  7. did: f1

Promise의 에러처리

Promise 내부에선 두 번쨰 인자인 reject를 이용하여 에러를 발생시킬 수 있다.

하지만 예외를 발생시킨 타이밍이 try catch 블럭이 아니다.
비동기 호출이기 때문에 콜스택이 비었을 때 타이머가 큐에 넣고 해당 큐의 내용이 콜스택이 쌓인 것이라 try 구문에서 catch가 안 된다.

따라서 실행 결과는 Uncaught (in Promise) error!이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function wait(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("error!");
}, sec * 1000);
});
}

try {
wait(3);
} catch (e) {
console.error(e);
}

Promise의 catch 함수

Promise의 호출뒤에 .catch(callback)을 통해 에러가 발생했을 떄 실행시킬 함수를 작성할 수 있다.

callback의 인자는 Promise내부에서 reject된 값이다.

아래 코드의 실행결과는 1st catch error!이다.

1
2
3
4
5
6
7
8
9
10
11
function wait(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject("error!");
}, sec * 1000);
});
}

wait(3).catch((e) => {
console.log("1st catch", e);
});

async/ await

async 함수의 리턴은 Promise이다.
따라서 Promise와 같은 방법으로 예외처리를 할 수 있다.

차이점은 await 키워드이다.

awaitasync함수 안에서만 사용할 수 있다.
Promisefullfill되거나 reject될 때 까지 async함수의 실행을 일시 정지한다.

await을 쓰지 않을 경우

아래의 코드는 await을 사용하지 않은 코드로 Promise와 같이 예외처리 해줄 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
async function myAsyncFunc() {
throw "myAsyncError!";
}

function myPromiseFun() {
return new Promise((resolve, reject) => {
reject("myError");
});
}

const result = await myAsyncFunc().catch((e) => {
console.log(e);
});
// myAsyncError!
const result2 = myPromiseFun().catch((e) => {
console.log(e);
});
// MyError!

await을 쓸 경우

아래의 코드는 await을 사용한 코드로 myAsyncFun 함수는 wait 함수의 Promisefullfill 될 때까지 함수의 실행을 일시정지한다.
때문에 try catch 구문을 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function wait(sec) {
return new Promise((resolve) => {
setTimeout(() => {
reject("wait Error");
}, sec * 1000);
});
}
async function myAsyncFun() {
console.log(new Date());
try {
await wait(3);
} catch (e) {
console.error(e);
}
console.log(new Date());
}
const result = myAsyncFunc();
//현재시간, 3초뒤 에러

느낀점

비동기 함수의 예외처리는 Promise Chaining을 고려하면 된다.

ref

throw
코드종(설명이 직관적이고 친절하시다..!)
await