비동기(Asynchronous)와 자바스크립트
비동기 처리란 특정 코드의 연산이 끝날 때까지 실행을 기다리지 않고 다음 코드를 먼저 실행하는 방법으로, Javascript에서는 주로 네트워크 요청(Ajax 요청), 파일 읽기/쓰기, 타이머 함수(setTimeout,setInterval), 이벤트 처리 등의 상황에서 사용된다. 만약 이러한 작업들이 동기식으로 처리된다면 다른 코드들의 실행이 블로킹되고 대기 시간이 길어지므로 웹 사이트의 성능과 사용자에게 부정적인 영향을 줄 수 있다. 비동기 처리를 통해 동시에 여러 가지 작업을 처리할 수 있고 기다리는 과정에서 다음 함수를 호출할 수 있다.
콜백 함수
비동기 처리를 위해 Javascript에서는 콜백 함수를 제공한다. 매개변수로 함수를 전달받아 내부에서 실행하는 함수이다. 필요한 시점에 매개변수로 받은 함수를 실행하여 자바스크립트의 비동기적인 작업을 처리하는 데 사용한다. 콜백 함수를 활용하면 비동기적인 처리를 순서대로 처리할 수 있도록(동기적으로) 바꿀 수 있다.
function timecheck(callback) {
setTimeout(() => {
console.log("1번 호출");
callback();
}, 3000);
}
function printview() {
console.log("2번 호출");
}
timecheck(printview);
콜백 지옥
각 함수를 순차적으로 실행하기 위해 콜백 함수만 사용한다면, 소위 얘기하는 콜백 지옥에 빠질 수 있다. 피자를 주문하는 과정을 생각해보자. 토핑을 고르고, 주문을 하고, 주문을 받고, 피자를 먹는다. 각 단계를 콜백 함수를 이용해 순서가 어긋나지 않도록 처리했다. 코드를 읽기도 힘들고, 콜백을 계속해서 작성해줘야 해서 불편하다.
chooseToppings(function (toppings) {
placeOrder(
toppings,
function (order) {
collectOrder(
order,
function (pizza) {
eatPizza(pizza);
},
failureCallback,
);
},
failureCallback,
);
}, failureCallback);
이러한 문제를 해결하기 위해, Promise를 사용할 수 있다.
프로미스(Promise)
Promise는 미래에 어떤 종류의 결과가 반환됨을 약속(promise)해주는 객체이다. 콜백 함수를 사용할 때보다 유연하고 편리하게 비동기 처리를 할 수 있다. Promise는 생성과 동시에 비동기 작업을 실행하고, 그 결과를 Promise 객체에 반환한다. 방금 확인한 예시에 Promise 방식을 적용하면, 다음과 같이 간결하게 작성할 수 있다.
// Promise + 화살표 함수
chooseToppings()
.then((toppings) => placeOrder(toppings))
.then((order) => collectOrder(order))
.then((pizza) => eatPizza(pizza))
.catch(failureCallback);
프로미스의 상태
프로미스는 다음 3가지 상태를 가질 수 있다.
대기(pending)
프로미스를 호출하면 프로미스 객체가 대기 상태가 되고, 프로미스에서 자체적으로 제공하는 resolve, reject라는 콜백 함수를 인자로 받아 상황에 맞게 작업 처리를 할 수 있다. 대기 상태 이후에 비동기 작업이 성공하면(이행) resolve(”성공!”)와 같이 결과(”성공!”)를 전달하고, 실패하면 reject(”실패!”)와 같이 오류 객체(Error(”실패”))를 전달한다.
let promise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("성공!");
} else {
reject("실패!"); // Error 객체 반환, 아래와 같이 Error 던져도 동일하게 동작
// throw new Error("실패");
}
});
이행(fulfill)
비동기 작업이 성공적으로 완료되어 결과값을 가지고 있는 상태이다. 이 때 resolve 콜백이 실행되고, 프로미스 객체는 이행 상태가 된다. then() 함수를 이용하여 resolve의 결과값을 받을 수 있다.
한편, then에서는 인자를 2개 받을 수도 있다. 첫 번째 인자는 resolve 콜백 함수의 결과값을, reject 콜백 함수의 오류 객체를 받는다. 그래서 성공 상태일때는 resolve의 결과값을, 실패 상태일때는 reject의 결과값을 받도록 한다.
// .then의 인자로 2개의 함수를 받는 예시
// 일반적으로는 .then에서 성공했을 때의 처리를 하고 .catch로 실패했을 때의 처리를 한다.
promise.then(
result => {
console.log("성공 콜백:", result);
},
error => {
console.log("실패 콜백:", error);
}
);
실패(reject)
비동기 작업이 실패하여 오류나 예외가 발생한 상태이다. 프로미스의 콜백 함수로 전달받은 매개변수인 reject 콜백이 실행되고 프로미스 객체는 실패 상태가 된다. catch() 함수를 이용하여 결과값을 받을 수 있다.
// 위의 예제를 다음과 같이 작성할 수 있음(일반적으로 사용하는 방법)
promise.then(
result => {
console.log("성공 콜백:", result);
})
.catch(error => {
console.log("실패 콜백:", error);
});
async와 await
프로미스로 가독성과 편리함을 높이기는 했지만, 이 또한 .then()을 계속해줘서 붙이기 때문에 프로미스 처리가 계속된다면 프로미스 지옥에 빠질 수도 있다. async/await 문법을 활용하여 더 편리하게 비동기 처리를 할 수 있다.
async/await 문법은 함수를 선언할 때 앞부분에 async 키워드를 작성하여 비동기 함수로 만든다. async가 앞에 붙은 함수는 프로미스를 반환한다. await는 async 함수 안에서만 동작하고 마치 동기식처럼 프로미스 처리가 끝날 때까지 기다린다.
// Promise를 작성하는 헬퍼 함수 작성
function helperPromise() {
return new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("성공!");
} else {
reject("실패!");
// throw new Error("실패");
}
});
}
// await는 단독으로 쓰일 수 없으므로, async로 감싼다.
async function useHelper() {
result = await helperPromise();
console.log("성공 콜백:", result);
}
useHelper();
async 예외 처리
async 함수 내부의 프로미스에서 예외가 되면(거부되면) throw로 에러를 반환한다. try/catch를 사용하면 프로미스가 거부될 때 해당 예외에 대한 추가 로직이나 오류 메세지를 출력할 수 있다.
async function useHelper() {
try{
result = await helperPromise();
console.log("성공 콜백:", result);
} catch (e) {
console.log("실패 콜백:", error);
}
}
참고 자료
🌐 자바스크립트의 핵심 '비동기' 완벽 이해 ❗
자바스크립트의 동기와 비동기 자바스크립트는 싱글 스레드 언어이기 때문에 한 번에 하나의 작업만 수행할 수 있다. 즉, 이전 작업이 완료되어야 다음 작업을 수행할 수 있게 된다. 우리가 프
inpa.tistory.com
Graceful asynchronous programming with Promises - Web 개발 학습하기 | MDN
Promises 는 이전 작업이 완료될 때 까지 다음 작업을 연기 시키거나, 작업실패를 대응할 수 있는 비교적 새로운 JavaScript 기능입니다. Promise는 비동기 작업 순서가 정확하게 작동되게 도움을 줍니
developer.mozilla.org