[JS] 동기와 비동기란? (Feat. 콜백, Promise, async/await)
동기(Synchronous)란? : 일이 순서대로 끝날 때까지 기다리는 방식
라면으로 예시를 들어보자면, 라면을 먹기 위해 물을 끓이기 시작하고, 물이 다 끓으면 면을 넣고 끓인 뒤 라면을 먹는다.
코드로 표현하자면 다음과 같다.
function boilWater() {
console.log('1. 물 끓이기 시작');
for (let i = 0; i < 3; i++) {
console.log('2. 물 끓이는 중 ...');
}
console.log('3. 물을 다 끓였어요!');
}
function makeRamen() {
boilWater();
console.log('4. 면 넣고 끓이기');
console.log('5. 라면 완성!');
}
makeRamen();
코드의 흐름을 보면, makeRamen이 호출되면서 먼저 boilWater 함수가 실행된다.
boilWater 함수에서 1번이 출력된 후, 2번이 3번 출력되고 나서 3번이 출력된다.
이 과정이 끝날 때까지 다음 코드는 실행되지 않으며, 작업이 끝나면 함수를 빠져나와 그 이후의 작업인 4번과 5번이 순서대로 출력된다.
// 출력
1. 물 끓이기 시작
2. 물 끓이는 중 ...
2. 물 끓이는 중 ...
2. 물 끓이는 중 ...
3. 물을 다 끓였어요!
4. 면 넣고 끓이기
5. 라면 완성!
그렇다면 비동기에서의 라면은 어떻게 끓일까?
비동기란(Asynchronous)? : 일이 끝나길 기다리지 않고 다른 일을 먼저 처리하는 방식
일단 라면을 먹기 위해 물을 끓일 것이다.
하지만 이전과 다른 점은, 라면을 끓일동안 게임을 하다 올 것이다.
게임을 하다가 물이 끓으면 타이머가 울리고, 그때 면을 넣고 끓인 후 라면을 먹으면 된다.
코드로 표현하자면 다음과 같다.
function boilWaterAsync() {
console.log("1. 물 끓이기 시작");
setTimeout(() => {
console.log("3. 물 끓었음");
console.log("4. 면 넣고 끓이기");
console.log("5. 라면 완성!");
}, 3000);
}
function doSomethingElse() {
console.log("2. 게임하기 🎮");
}
boilWaterAsync();
doSomethingElse();
먼저 boilWaterAsync가 호출되고 1번이 출력된다.
그 후 setTimeout으로 3초 뒤 실행하게끔 3, 4, 5번이 출력되게끔하고,
그 사이에 doSomethingElse가 호출되면서 2번이 출력된다.
그리고 3초 후 3, 4, 5번이 순서대로 출력된다.
1. 물 끓이기 시작
2. 게임하기
(3초 후 ...)
3. 물 끓었음
4. 면 넣고 끓이기
5. 라면 완성!
그럼 이제 비동기에서 중요한 개념들인 콜백, Promise, async/await은 뭘까?
콜백 함수란? : 콜백은 “나중에 실행할 함수”를 인자로 전달해서, 어떤 일이 끝난 뒤 실행되게 하는 방식
function cook(callback) {
console.log("요리 시작");
setTimeout(() => {
console.log("요리 끝!");
callback();
}, 2000);
}
cook(() => {
console.log("먹자! 🍽️");
});
위의 코드를 보면, cook이 먼저 호출되고 '요리 시작'이 출력된다.
그 후 2초 뒤에 '요리 끝'이 출력된 다음 아규먼트로 넘겨준 callback 함수가 실행되면서 '먹자!'가 출력된다.
콜백 함수에서의 주의점은 콜백 지옥(Callback Hell)을 조심해야하는데,
이는 함수 안에 함수, 또 함수 안에 함수와 같은 형태로 가독성이 떨어지고 디버깅이 어려워진다.
function login(id, callback) {
setTimeout(() => {
console.log("로그인 성공");
callback(id);
}, 1000);
}
function getUserInfo(id, callback) {
setTimeout(() => {
console.log(`사용자 정보 불러오기: ${id}`);
callback({ id, name: "Alice" });
}, 1000);
}
function getFriends(user, callback) {
setTimeout(() => {
console.log(`${user.name}의 친구 목록`);
callback(["Bob", "Charlie"]);
}, 1000);
}
// 콜백 지옥 시작
login("user123", (id) => {
getUserInfo(id, (user) => {
getFriends(user, (friends) => {
console.log("친구 목록:", friends);
});
});
});
Promise란? : 작업이 끝났을 때 성공/실패 결과를 주겠다고 약속하는 객체
Promise는 .then()과 .catch()를 이용해서 결과를 처리할 수 있다.
function cook() {
return new Promise((resolve) => {
console.log("요리 시작");
setTimeout(() => {
console.log("요리 끝!");
resolve("라면 완성 🍜");
}, 2000);
});
}
cook().then((result) => {
console.log(result); // 라면 완성 🍜
});
cook이 호출되면서 '요리 시작'이 출력되고, 2초 후 '요리 끝'이 출력된다.
그 순간 resolve("라면 완성 🍜")가 호출되어 Promise가 성공(이행)되고, .then()이 그 값을 받아 '라면 완성 🍜'을 출력한다.
하지만 Promise를 사용하면서도 주의할 점은 Promise 체인이 길어지면 가독성이 떨어진다.
function login(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("로그인 성공");
resolve(id);
}, 1000);
});
}
function getUserInfo(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`사용자 정보 불러오기: ${id}`);
resolve({ id, name: "Alice" });
}, 1000);
});
}
function getFriends(user) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${user.name}의 친구 목록`);
resolve(["Bob", "Charlie"]);
}, 1000);
});
}
login("user123")
.then(getUserInfo)
.then(getFriends)
.then((friends) => {
console.log("친구 목록:", friends);
});
async/await 이란? : Promise를 더 간단하고 동기 코드처럼 보이게 만드는 문법
택배로 예시를 들어보면, Promise 같은 경우는 택배가 오면 그때 열어보는 것이고 async/await은 택배가 올 때까지 기다렸다가 열어보는 것이라고 할 수 있다.
function cook() {
return new Promise((resolve) => {
console.log("요리 시작");
setTimeout(() => {
console.log("요리 끝!");
resolve("라면 완성 🍜");
}, 2000);
});
}
async function eat() {
const food = await cook(); // cook() 끝날 때까지 기다림
console.log(food);
console.log("먹자! 😋");
}
eat();
코드를 보면, 먼저 eat이 호출되고 cook이 실행된다.
cook이 전부 실행될 때 까지 기다린 후에 food가 출력되고 마지막으로 '먹자'가 출력된다.
food에는 cook에서 resolve가 호출되면서 Promise가 성공했기 때문에 '라면 완성'이 저장된다.
가독성도 좋기 때문에 많이 쓰이는 문법이다.
요약
콜백 함수 | 함수 안에 함수 |
Promise | 성공/실패를 나중에 알려주는 약속 |
async / await | Promise를 더 깔끔하게 쓰는 방식 |