async / await
자바스크립트에서 비동기 작업을 처리할 때 가장 기본적인 방식은 Promise
와 .then()
을 사용하는 것이다. 하지만 .then()이 중첩되거나 체이닝이 길어지면 코드의 흐름을 따라가기 어려워지고, 가독성도 떨어지게 된다. 이러한 문제를 해결하기 위해 도입된 문법이 async와 await이다.
async / await
은 비동기 코드를 동기 코드처럼 순차적으로 작성할 수 있도록 해주는 문법
이다. 복잡한 비동기 흐름도 간결하게 표현할 수 있어 코드의 가독성과 유지보수성을 크게 높여준다.
- .then() 체이닝보다 가독성이 좋다
- 예외 처리도 try-catch로 간단하게 관리할 수 있다
- 중첩 없이 위에서 아래로 흐르는 구조로 코드를 작성할 수 있다
결과적으로 async / await은 가독성, 예외 처리, 유지보수
측면에서 우수하기 때문에 현재 가장 많이 사용되는 비동기 처리 방식 중 하나이다.
async / await
├── async / await
├── try-catch (예외 처리)
└── Promise.all()
async / await
async function 함수이름() {
const result = await 비동기함수();
return result;
}
// Promise와 .then()을 사용한 기본적인 비동기 처리 코드
// 값이 있는 Promise(3초 후 메시지를 반환받아 출력하는 구조)
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("3초가 지났습니다");
}, ms);
});
};
const start = () => {
delay(3000).then((res) => {
console.log(res);
});
};
start(); // 3초가 지났습니다
- 값을 활용하는 Promise 구조로, 결과값을 활용해야 할 때 적합하다.
- resolve("3초가 지났습니다")는 문자열을 반환하며, .then(res)의 res는 이 값을 받는다.
// Promise와 .then()을 사용한 기본적인 비동기 처리 코드
// 값이 없는 Promise(3초 후 실행만 하는 구조)
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
const start = () => {
delay(3000).then(() => {
console.log("대기");
});
};
start(); // 대기
- delay 함수는 지연 시간을 인수로 받아 setTimeout을 Promise로 감싼 비동기 함수이다. 일정 시간이 지난 후 resolve()를 호출해 다음 흐름으로 넘어가도록 한다.
- resolve()에 값을 전달하지 않으면, .then()은 별도의 인자 없이 실행만 한다. 주로 단순 대기 후 작업을 실행할 때 사용하는 구조이다.
- start 함수는 delay가 끝난 뒤 .then() 메서드를 통해 "대기"라는 메시지를 출력한다.
- 위 코드는 async/await 문법으로 더 직관적으로 변경할 수 있다.
// async만 사용하여 반환값을 .then()으로 처리하는 경우
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
const start = async () => {
return "대기";
};
start().then((result) => {
console.log(result);
});
// 대기
async
는 비동기 함수임을 나타내는 키워드로, 함수 앞에 붙이면 해당 함수는 자동으로 Promise를 반환하는 비동기 함수가 된다.- async 함수 내부에서 return한 값은 자동으로 Promise로 감싸져 반환되며, 이는 마치 resolve()를 호출한 것과 같은 효과를 낸다. 이 결과는 .then()을 통해 이어서 처리할 수 있다.
// async, await을 함께 사용해, await으로 비동기 흐름을 제어하는 경우
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
const start = async () => {
await delay(2000); // delay 함수가 처리 완료될 때까지 기다림
console.log("대기");
};
start();
await
은async 함수 내부
에서만 사용할 수 있는 키워드이다.await
앞에 오는 Promise가 처리될 때까지 기다린 뒤, 다음 줄의 코드를 실행한다.- 덕분에 비동기 작업도 동기 코드처럼 위에서 아래로 순차적으로 작성할 수 있게 된다.
try-catch
async / await을 사용할 때는 try-catch
문을 활용해 비동기 작업 중 발생할 수 있는 에러를 안전하게 감지하고 처리할 수 있다. 여러 개의 await가 있는 경우, 한 곳에서 에러를 처리할 수 있어 코드가 깔끔해진다. 코드 흐름이 중단되지 않고, 예외가 발생해도 이후 로직을 유연하게 이어갈 수 있다.
이는 Promise의 .catch()와 같은 역할을 하며, 예외 상황을 보다 구조적으로 다룰 수 있게 해준다.
async function 작업() {
try {
const result = await something();
console.log(result);
} catch (err) {
console.error(err);
}
}
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
const start = async () => {
try {
await delay(2000);
console.log("대기");
} catch (error) {
console.log(error);
}
};
start();
- try 블록 안의 코드가 실행되며, 이 과정에서 에러가 발생하면 즉시 catch 블록으로 흐름이 넘어간다.
- 발견된 에러는 catch(error)의 error 객체에 담기며, 이를 통해 에러의 원인을 확인할 수 있다.
// async / await을 사용한 비동기 작업의 순차 실행
const workA = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workA");
}, 5000);
});
};
const workB = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workB");
}, 3000);
});
};
const workC = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workC");
}, 10000);
});
};
const start = async () => {
try {
let resultA = await workA(); // 5초 대기
let resultB = await workB(); // 그 다음 3초 대기
let resultC = await workC(); // 마지막 10초 대기
console.log(resultA);
console.log(resultB);
console.log(resultC);
} catch (error) {
console.log(error);
}
};
start();
// workA
// workB
// workC
- workA, workB, workC 함수는 각각 5초, 3초, 10초 후에 문자열을 반환하는 비동기 함수이다. 내부에서는 setTimeout()과 Promise를 사용해 일정 시간 뒤 resolve()를 호출한다.
- start() 함수는 async 함수이며, 내부에서 await을 사용할 수 있다. 각 await는 해당 Promise가 완료될 때까지 다음 줄로 넘어가지 않고 대기한다.
- 따라서 작업은 순차적으로 실행되며, 전체 실행 시간은 5초 + 3초 + 10초 = 약 18초가 소요된다. 모든 작업이 완료된 후, 18초 뒤에 결과가 차례대로 콘솔에 출력된다.
Promise.all()
Promise.all()
은 여러 개의 비동기 작업(Promise)을 병렬로 실행
하고, 모든 작업이 성공(Fulfilled)해야 결과를 배열
로 반환하는 메서드이다. 전달된 프로미스 중 하나라도 실패(Rejected)하면 전체가 실패로 간주되며, catch 블록으로 흐름이 이동한다.
Promise.all()
은 병렬 처리를 통해 전체 실행 시간을 단축할 수 있는 유용한 방법이다. 각 작업의 소요 시간과 상관없이, 가장 오래 걸리는 작업이 끝나는 시점에 모든 결과를 받을 수 있다. 단, 하나라도 실패하면 전체가 실패로 처리되므로 주의가 필요하다.
const workA = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workA");
}, 5000);
});
};
const workB = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workB");
}, 3000);
});
};
const workC = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("workC");
}, 10000);
});
};
const start = async () => {
try {
let results = await Promise.all([workA(), workB(), workC()]);
result.forEach((res) => console.log(res));
} catch (error) {
console.log(error);
}
};
start();
// workA
// workB
// workC
Promise.all()
은 세 개의 작업 workA, workB, workC를 동시에 실행한다.- 가장 오래 걸리는 workC가 끝나는 약 10초 후, 세 작업의 결과가 배열에 담겨 반환된다.
- 결과 배열의 순서는 [ "workA", "workB", "workC" ]로, 작업의 실행 완료 순서가 아닌 호출된 순서를 따른다.
- 하나라도 실패하면, 해당 에러가 catch 블록으로 전달되고 다른 결과는 무시된다.