어떤 함수에 전달되는 함수를 의미한다. 간단한 예제는 다음과 같다.
function order(food, callback) {
console.log(`${food} 도착`);
callback();
}
function eat() {
console.log('먹는다');
}
order('짜장면', eat);
결과
짜장면 도착
먹는다
callback 함수의 단점은,
1) 가독성이 떨어진다.
2) 후속 처리가 어렵다.
가독성이 떨어진다는 건 다음 사진을 참고하면 되겠다. (콜백 지옥)

참고로 며칠 전에, 학부 2학년 때 짠 코드를 봤는데 이런 식으로 짜여있더라(...) 언젠가 리팩토링을 해야 할 텐데, 엄두가 나질 않는다. 말 그대로 가독성이 안 좋기 때문이다.
후속 처리가 어렵다는 건, 다음 코드를 참고할 수 있겠다.
let global = 0;
function setGlobalWith100() {
console.log('setGlobalWith100 started');
setTimeout(() => {
global = 100;
}, 1000);
console.log('setGlobalWith100 ended');
return global;
}
const result = setGlobalWith100();
console.log(result);
console.log(global);
이 코드에서는, result와 global의 로그를 찍는 부분이 실행된 후 task queue에 있던 setTimeout이 실행된다. 따라서 이 코드에서는 global = 100에 접근할 수 없다.
즉, 비동기 처리 결과를 외부로 반환하거나 상위 스코프의 변수에 할당할 수 없고, 서버의 응답과 같이 비동기 함수의 결과값에 대한 처리는 비동기 함수 내부에서만 수행해야 한다.
이 단점을 극복한 API가 Promise이다.
프로그래밍에서 Promise란 어떤 프로그램이 실행되는 흐름에서 수행해야 할 어떤 작업의 처리 결과(성공/실패)에 대한 결과값이 담길 객체로, 해당 작업을 시작하는 시점에서는 아직 처리가 성공했는지, 실패했는지에 대해 알 수 없다. 이러한 처리 결과는 미래의 어느 시점에 성공/실패 중 하나의 형태로 응답받게 된다.
java의 future와 비슷하다고 한다.
간단한 예제는 다음과 같다.
// Promise를 통해 비동기 처리 작업 수행하기
const executor = (resolve, rejected) => {
// 1. 요청이 성공했을 경우
console.log('1');
setTimeout(() => {
console.log('2');
resolve('원하는 데이터값');
}, 1000);
};
const promise = new Promise(executor);
console.log('middle');
// promise 객체가 사용할 수 있는 후속처리(체이닝 메서드)
// then() -> 작업이 성공했을 경우 처리할 로직 작성 부분
promise.then((response) => {
console.log(response);
});
// The callback to execute when the Promise is resolved
// resolve()에 있는 '요청 처리 성공 결과' 값이 response에 전달됨.
결과

const executor = (resolve, rejected) => {
// 1. 요청이 성공했을 경우
resolve('요청 성공에 따른 결과 데이터');
// 2. 요청이 실패했을 경우
rejected('요청 실패에 따른 에러 메시지');
};
const promise = new Promise(executor);
// 별도의 변수(afterThen)를 사용하지 않고 작성
promise
.then((response) => { // then은
console.log(response);
}) // Promise 객체 반환
.catch((error) => {
console.error('캐치된 에러: ', error);
}); // Promise 객체 반환
.finally(() => console.log('요청 수행 완료'));
fetch는 promise 객체를 반환하여, then, catch를 이용한다. input 값으로 URL이 활용된다. 자세한 건 fetch 문서 참조.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => response.json())
.then(json => console.log(json))
첫 then에서 response를 받아와 response.json()을 반환한다. (직렬화)
response.json()을 json으로 받아와 콘솔 로그를 남긴다.
역직렬화를 생략해주는 라이브러리로는 axios가 있다.
async & await는 ES8 이후 도입된 키워드로, 별도의 후속처리 핸들러(then()) 없이 비동기 작업을 처리하는 코드를 마치 동기적인 방식처럼 작성하여 가독성을 높일 수 있다.
async function findUser() {
try {
const post = await getPost(1); // /posts/1
const user = await getUser(post); // /users/:userId
printUser(user);
} catch (err) {
console.error('실패:', err.message);
}
}
console.log('findUser 호출 전');
findUser(); // findUser는 비동기 함수로 동작
console.log('findUser 호출 후');
결과
findUser 호출 전
findUser 호출 후
User: {id: 1, name: '김하늘', email: 'haneul.kim@example.com'}
findUser 속 getPost, getUser의 실행 순서를 보장해준다.