JS - callBack, Promise, async/await
이 셋은 어디에 쓰나요?
=> 보통, 비동기 프로그래밍을 구현하기 위해 사용된다.
오늘은 비동기 프로그래밍을 위해 CallBack Function, Promise, async/await가 뭐고, 뭘 쓰면 되는지 간단하게 보자. (Generator도 존재하는 것으로 알고 있으나 다 담지 못했다… 차후에 더 공부해볼 예정이다.)
먼저 CallBack Function과 Promise를 비교해보자.
Promise vs Callback function
CallBack 함수란?
- 다른 함수의 전달인자(argument)로 넘겨주는 함수를 말한다.
- 또한, CallBack 함수로 무조건 비동기 프로그래밍만 하는 건 아니다. 콜백 함수를 써서 비동기 프로그래밍을 할 수도 있구나 라고 이해함이 옳다.
아래는 CallBack 함수를 통한 간단한 비동기 프로그래밍의 구현이다.
findUserAndCallBack(1, function (user) {
console.log("user:", user);
});
function findUserAndCallBack(id, cb) {
setTimeout(function () {
console.log("waited 0.1 sec.");
const user = {
id: id,
name: "User" + id,
email: id + "@test.com",
};
cb(user);
}, 100);
}
다만, Callback을 썼을 때에는 Promise 방식보다 단점이 여러 개 존재한다.
-
CallBack hell => 간단한, 비동기 구현이면 상관이 없긴 한데, 계속 받은 결과값을 꼬리물기로 활용하면 지옥의 코드가 탄생한다. 아래와 같은 코드를 CallBack hell이라고 한다.
(도망가고 싶다.)
ㅤ
ㅤ
ㅤ -
에러 처리가 곤란하다는 것이다. 예를 들어, 이런 코드가 있다고 해보자.
try {
setTimeout(() => {
throw new Error("Error!");
}, 1000);
} catch (e) {
console.log("캐치된 에러", e);
}
놀랍게도 catch 문이 실행되지 않는다!
예외는 함수를 호출한 상위로 거슬러 전파된다. 지금, setTimeout 내부의 에러를 발생시키는 함수는 try-catch 안에서 단순히 정의된 상황이다.
실제로 함수는 setTimeout에 의해서 실행된다. 그렇기에, try-catch 안에서 발생한 오류가 아니므로 예외를 감지할 수 없는 상황이 발생한 것이다. ㅤ ㅤ ㅤ ㅤ ㅤ ㅤ
자 이제, 반대로 Promise를 보자.
Promise의 경우 성공했다는 fullfilled, 실패했다는 rejected, 진행중이라는 pending이라는 3가지 상태가 존재한다.
또한, then과 catch를 이 것들과 연계하여 비동기 처리를 구성하는데 성공하면 then을 통해 코드를 내려가면서 계속 값을 가져가기 때문에 복잡한 callback hell을 방지할 수 있다. 그리고 catch를 통해 에러 핸들링또한 가능하다.
아래의 코드는 두 가지를 담고 있는 코드이다.
- 기본적인 Promise 사용 코드
- Promise.catch를 통한 에러처리 코드
const condition = true;
//resolve는 성공했을 때 실행되는 함수이다.
//reject는 실패했을 때 실행되는 함수이다.
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve("resolved");
} else {
reject("rejected");
}
});
promise
.then((res) => {
console.log(res);
})
.catch((error) => {
console.error(error);
});
결론적으로는, Callback의 단점을 해결한 Promise가 더 좋다고 볼 수 있다.
ㅤ ㅤ ㅤ ㅤ ㅤ ㅤ
async/await vs promise
async/await의 경우 위의 두 방법의 단점을 어느정도 해소하고자 어느정도 최근에 나온 비동기 프로그래밍 문법이다.
아래는, 자주쓰는 async/await로 구현한 request 코드이다.
const API_END_POINT = "https://~~~~";
export const request = async (nodeId) => {
try {
const res = await fetch(`${API_END_POINT}/${nodeId ? nodeId : ""}`);
if (!res.ok) throw new Error("서버의 상태가 이상합니다!");
return await res.json();
} catch (e) {
throw new Error(`무언가 잘못 되었습니다! ${e.message}`);
}
};
둘의 차이는 다음과 같다.
- 에러 핸들링
Promise를 활용할 시에는.catch()문을 통해 에러 핸들링이 가능하지만,async/await은 에러 핸들링 할 수 있는 기능이 없어try-catch()문을 활용하는 모습을 위의 코드로 볼 수 있다.
- 코드 가독성
Promise는.then()이 쭉 내려가는, “then 지옥”이 발생할 수도 있다.- 코드가 길어지면 길어질수록,
async/await를 활용한 코드가 가독성이 좋다. async/await은 비동기 코드가 동기 코드처럼 읽히게 해준다. 코드 흐름을 이해 하기 쉽다.
당장 위의 코드도, fetch를 통해 받은 데이터를 상수 res에 저장하고 동기 코드처럼 쭉 내려가면서 코드가 진행된다.
이런 점들이 있다보니, 필자의 경우 비동기 코드를 짜야 할 때 async/await 방식을 통해 구현하고 있다.