callback 함수는 상황에 따라 크게 2가지로 분류할 수 있다.
1. 다른 함수의 인자로 이용되는 함수
2. 이벤트에 의해 호출되어지는 함수
1번의 경우부터 살펴보자
아래의 예시를 보면 add함수의 매개변수로 다른 함수를 넣어줘서 add함수를 실행할 때 다른 함수까지 한번에 실행해보았다.
function add(a,b,c) {
const result = a + b
c(result)
}
add(6,12,(x)=>{
console.log("합계는" + x +"입니다")
})
// "합계는 18입니다" 출력됨
이렇게 콜백함수를 사용해주면 콜백함수의 실행 순서를 조절할 수가 있는데, 과거 Promise 또는 async/await 라는 기능이 생겨나기 전에는 비동기의 동기처리를 콜백함수를 이용해서 처리하였다고 한다.
2번의 경우도 살펴보자
<button onclick="changeName()"></button>
위처럼 버튼의 클릭 이벤트에 changeName()라는 함수가 호출되는 경우인데, 이 경우에도 callback함수라고 한다.
또한 callback함수는 동기/비동기적 함수로 분류할 수 있는데 이번 페이지에서 비동기에 대해 더 알아보도록 하자!
위에서 언급했듯이 과거에는 Promise나 async/await가 없었기 때문에, callback 함수로 비동기함수를 동기적으로 처리했다. 아래의 예시를 보고 쉽게 이해해보자!
function addEventListener(aaa, bbb) {
// 셋팅된 API 주소로 요청!
const res = 20; // 셋팅된 API 주소로부터 받아온 결과라 가정
if (aaa === "load") {
bbb(res); // 데이터 받아왔으니까, 콜백함수 실행!
}
}
const myCallback = () => {
const aa = new XMLHttpRequest();
aa.open("get", `http://numbersapi.com/random?min=1&max=200`);
aa.send();
aa.addEventListener("load", (res) => {
console.log(res); // API 요청 결과
const num = res.target.response.split(" ")[0]; // 랜덤 숫자
const bb = new XMLHttpRequest();
bb.open("get", `https://koreanjson.com/posts/${num}`);
bb.send();
bb.addEventListener("load", (res) => {
console.log(res); // API 요청 결과
const userId = JSON.parse(res.target.response).UserId;
const cc = new XMLHttpRequest();
cc.open(
"get",
`https://koreanjson.com/posts?userId=${userId}`
);
cc.send();
cc.addEventListener("load", (res) => {
console.log(res); // 최종 API 요청 결과
});
});
});
};
위처럼 콜백함수를 연달아 쓰게되면 코드의 들여쓰기 수준이 감당하기 힘들 정도로 깊어지는데 이를 콜백지옥이라고 부른다!
비동기적인 작업을 수행하기 위해 이런 형태가 자주 등장하는데, 가독성이 떨어지면서 코드를 수정하기 어렵다는 문제점이 있다.
이를 해결하기 위해 promise가 등장하게 되었다!!
new Promise는 아래의 예시에서 볼 수 있듯이 .then() 과 .catch() 기능을 보유하고 있다. then과 catch는 한쌍으로 항상 함께 다니는데 then은 응답을 기다렸다가 응답 값을 반환해주는 역할을 하고, catch는 응답을 기다렸지만 실패했을 때 에러 상황을 반환해주는 역할을 한다.
new Promise((성공했을때실행시킬함수, 실패했을때실행시킬함수) => {
try {
// 여기서 API 요청을 한다면?
const response = "철수"; // 백엔드에서 "철수" 데이터 받아옴
성공했을때실행시킬함수(response); // 성공하면 이거 실행
} catch (error) {
실패했을때실행시킬함수("실패했습니다!!"); // 실패하면 이거 실행
}
})
.then((res) => {
console.log(res); // 철수
})
.catch((err) => {
console.log(err); // 실패했습니다!!
});
보통 axios나 fetch등을 이용하여 통신할 때 promise라는 문구를 많이 볼 수 있는데, 이는 axios와 fetch가 promise를 지원하는 기능을 보유하고 있기 때문이다. 다시 말해, promise를 이용할 수 있는 라이브러리 정도 라고 생각하면 된다!
axios를 직접 만들어 본다고 가정하고 코드를 짜보면 아래와 같지 않을까?!
const axios = {
get: () => {
return new Promise((성공시, 실패시) => {});
},
};
위 콜백함수에서 통신요청을 했던 것을 promise를 이용해서 한다면 어떻게 표현되는지 살펴보자
const myPromise = () => {
axios
.get(`http://numbersapi.com/random?min=1&max=200`)
.then((res) => {
axios
.get(`https://koreanjson.com/posts/${num}`)
.then((res) => {
axios.get(
`https://koreanjson.com/posts?userId=${userId}`
).then((res)=>{
// res 최종 결과
});
});
});
};
어? 이것도 콜백지옥이랑 다를게 뭐지? 싶을텐데 위 코드는 가독성을 배제하고 작성한 것이기 때문에 그렇게 보이는게 당연하다.
이를 다시 가독성 고려해서 작성해 보면 아래와 같다.
const myPromise = () => {
axios
.get(`http://numbersapi.com/random?min=1&max=200`)
.then((res) => {
return axios.get(`https://koreanjson.com/posts/${num}`);
})
.then((res) => {
return axios.get;(
`https://koreanjson.com/posts?userId=${userId}`
);
})
.then((res) => {
// res 최종 결과
});
};
마지막으로 가장 최근에 나온 개념인 async / await에 대해 알아보자.
복잡할 것 없이 이는 promise를 대체해서 간편하게 사용할 수 있는 기능을 가지고 있다고 생각하면 된다!
하지만 주의해야 할 점 이 있는데!!!
async/await는 아무곳에서나 존재할 수 있는 것이 아니다.
다시 말해, 아무데나 쓰인다고 뒤의 과정을 기다려 주는 것이 아니라는 말이다.
그럼 언제 쓰일 수 있는걸까?
바로 promise를 return하는 곳에서만 제기능을 발휘하기 때문에 promise를 return하는 곳에서만 사용 가능하다!
그렇다면 promise를 return하는 곳은 어딘데??
바로 axios나 fetch등 대표적으로 promise를 지원하는 기능을 가진 함수 뒤에서만 가능하다.
아래의 코드는 위에서 했던 과정을 async / await를 이용해서 표현해본 것이다. 위에서 봤던 과정들보다 훨씬 간편하고 가독성도 좋아진 것을 볼 수 있다.
const myAsyncAwait = async () => {
await axios.get(`http://numbersapi.com/random?min=1&max=200`);
await axios.get(`https://koreanjson.com/posts/${num}`);
await axios.get(
`https://koreanjson.com/posts?userId=${userId}`
);
};