JS - async function, Promise를 활용하여 비동기 작업 수행하기, 비동기 작업을 동기로 처리하기
서론
각각의 작업 a, b, c가 있다.
a는 5초가 걸리고, b는 10초, c는 3초가 걸린다.
작업을 순차적으로 기다리면서 처리하게 된다면 이 작업은 총 5+10+3 으로 총 18초가 걸리게 된다.
그럼 각각의 작업을 일괄적으로 처리하게 하려면 어떻게 해야 할까?
정답은 비동기 작업으로 해당 작업들을 수행하면 된다.
이 포스팅에서는 비동기 방법 중 Promise 객체에 대해 다룬도록 한다.
Promise
개념
- 비동기 작업의 완료 또는 실패에 따른 결과 값을 나타내는 객체
- 비동기 메서드에서 마치 동기 메서드처럼 반환할 수 있음
- 세 가지의 상태가 존재
- 대기(pending) : 초기 상태
- 이행(fulfilled) : 비동기 작업 성공
- 거부(rejected) : 비동기 작업 실패
- 상태에 따라 then 메서드에 의해 작업들의 처리 결과를 호출
기본 구문
기본 구문은 모질라 공식문서의 샘플 코드를 사용하였다.
let myFirstPromise = new Promise((resolve, reject) => {
// 우리가 수행한 비동기 작업이 성공한 경우 resolve(...)를 호출하고, 실패한 경우 reject(...)를 호출합니다.
// 이 예제에서는 setTimeout()을 사용해 비동기 코드를 흉내냅니다.
// 실제로는 여기서 XHR이나 HTML5 API를 사용할 것입니다.
setTimeout(function () {
resolve("성공!"); // 와! 문제 없음!
}, 250);
});
myFirstPromise.then((successMessage) => {
// successMessage는 위에서 resolve(...) 호출에 제공한 값입니다.
// 문자열이어야 하는 법은 없지만, 위에서 문자열을 줬으니 아마 문자열일 것입니다.
console.log("와! " + successMessage);
});
- Promise() : 새로운 Promise 객체를 생성하는 생성자, 주로 프로미스를 지원하지 않는 함수를 감쌀 때 사용
- Promise.resolve() : 비동기 작업이 처리된 결과 값이 포함된 Promise 객체를 반환, then을 사용할 경우 then 메서드에서 주어진 값을 반환
- Promise.reject(reason) : 비동기 작업이 실패한 Promise 객체를 반환
- setTimeout : 비동기적인 동작을 수행하는 함수, 숫자는 밀리초 단위 (1초 = 1000)
* resolve, reject는 생성자에서 주어진 인자(resolve, reject)를 사용하여 값을 반환할 수도 있다. 예) resolve("성공!")
async function
- 하나의 비동기 함수를 정의하여 AsyncFunction 객체를 반환하는 함수
- 암시적으로 Promise 객체를 사용하여 결과를 반환함
- 만약 반환값이 명시적으로 Promise가 아닐 경우, 암묵적으로 Promise로 감싸짐
- async 함수에는 await 키워드를 사용하여 동기 처리와 같은 방식으로 작업을 할 수 있음
- await 사용 시, 실행을 일시 중지하고 전달된 Promise의 결과를 기다린 다음 async 함수의 실행을 다시 시작하고 완료후 값을 반환함
- await 키워드는 async 함수에만 사용할 수 있음
- async 외의 함수에서 사용 시 Syntax Error 발생
기본 구문
기본 구문은 모질라 공식문서의 샘플 코드를 사용하였다.
async function name([param[, param[, ... param]]]) {
statements
}
- name : 함수의 이름
- param : 함수에게 전달되기 위한 인자의 이름
- statements : 함수 본문을 구성하는 내용, 이 곳에 await 키워드를 사용할 수 있음
예제1 : 비동기 작업 일괄 처리하기
각각의 작업을 모두 비동기로 일괄 처리해보자.
- 작업 A : 소요시간 5초
- 작업 B : 소요시간 10초
- 작업 C : 소요시간 3초
모두 작업을 일괄적으로 비동기로 처리하여야 하기 때문에
작업의 순서는 '작업C > 작업A > 작업B' 순으로 처리되어야 한다.
<script>
window.onload = function() {
// 5초 뒤 'a'를 반환하는 비동기 작업
workA();
// 10초 뒤 'b'를 반환하는 비동기 작업
workB();
// 3초 뒤 'c'를 반환하는 비동기 작업
workC();
};
function workA() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve( console.log('a') );
},5000);
});
}
function workB() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve( console.log('b') );
},10000);
});
}
function workC() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve( console.log('c') );
},3000);
});
}
</script>
- 콘솔 확인 화면 : 정상적으로 c, a, b 작업이 순차적으로 진행되는 것을 확인할 수 있다.
예제2 : 비동기 작업을 동기로 처리하기
예제1번과는 다르게 특정 비동기 작업을 흐름에 맞게 동기적으로 처리해야하는 경우가 있다.
예를 들면, 로그인 세션을 확인하는 과정에서 세션 정보를 확인 후 페이지를 보여줘야 한다면?
세션 확인은 서버와 통신해야하기 때문에 비동기로 처리할 수 밖에 없다.
이런 경우, 세션을 받고 나서 작업을 수행하여야 한다.
그렇다면 예제1과 같은 작업에서 비동기로 처리된 작업을 동기적으로 진행해야 하는 경우를 알아보자.
- 작업 A : 소요시간 5초
- 작업 B : 소요시간 10초
- 작업 C : 소요시간 3초
위와 같은 경우라면 실제 실행되는 과정은 '작업A > 작업C > 작업B' 순으로 진행 되게 된다.
우리는 작업B가 포함된 전체 작업을 '작업A > 작업B > 작업C' 순으로 동기적으로 처리하려 한다.
우선 await 메커니즘을 살펴보자.
- 작업 A에서 5초를 소요하고 'a' 를 반환한다.
- 작업 B에서 비동기 작업으로 10초를 소요하여 'b'를 반환해야 하지만,
await 키워드를 사용하여 전체 작업을 일시 중지 하고 Promise의 반환을 기다린 다음 전체 작업을 다시 시작한다. - 작업 B의 Promise가 반환되면 작업 C에서 3초를 소요하고 'c'를 반환한다.
<script>
window.onload = async function() {
// 5초 걸리는 동기 작업
workA();
// 10초 걸리는 비동기 작업 : await를 사용하여 동기적으로 처리하도록 변경
await asyncWorkB();
// 3초 걸리는 동기 작업
workC();
};
// 5초 뒤 'a' 를 콘솔에 출력하는 함수
function workA() {
setTimeout(function() {
console.log('a');
}, 5000);
}
// 10초 뒤 'b' 를 콘솔에 출력하는 함수
async function asyncWorkB() {
return new Promise(function(resolve) {
setTimeout(function() {
resolve( console.log('b') );
},10000);
});
}
// 3초 뒤 'c' 를 콘솔에 출력하는 함수
function workC() {
setTimeout(function() {
console.log('c');
}, 3000);
}
</script>
- 콘솔 확인 화면 : 정상적으로 a, b, c 작업이 순차적으로 진행되는 것을 확인할 수 있다.
예제3. then을 사용하여 비동기 작업 처리하기
이번엔 then을 사용하여 비동기 작업을 수행하도록 해보자.
아래의 코드는 1초 뒤 비동기 작업이 성공된다는 메시지가 반환되는 코드이다.
<script>
window.onload = function() {
// Promise then 사용
asyncFunc()
// .then을 사용하여 주어진 결과를 반환할 수 있음
.then(result => {
console.log('결과:', result);
})
// .catch를 사용하여 에러를 제어할 수도 있음
// .catch(error => {
// console.error('오류:', error);
// });
};
// 비동기 작업 함수
function asyncFunc() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = "비동기 작업 성공";
resolve(data); // 성공 시 반환되는 값
}, 1000);
});
}
</script>
- 콘솔 확인 화면 : then을 사용하여 비동기 작업을 수행하고 then 메서드 내에 있는 콘솔 출력으로 값을 반환
비동기 작업의 성공, 실패 반환하기
비동기 작업에서 성공과 실패 상태를 확인하여 상황에 따라 값을 다르게 반환할 수 있다.
주로 에러를 확인하기 위해 사용하는데, 다음과 같은 코드를 통해 실패의 반환값을 확할 수 있다.
비동기 작업의 절차는 위에 모두 다뤘기 때문에 여기서 추가 설명은 따로 하지 않겠다.
window.onload = function() {
// Promise then 사용
asyncFunc()
.then(result => {
console.log('결과:', result);
})
.catch(error => {
console.error('오류:', error);
});
};
function asyncFunc() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = "비동기 작업 성공";
// 조건을 주어 결과에 따라 성공과 실패를 확인할 수 있다.
if (data > 0.5) { // 의도적으로 문자열을 숫자와 비교하여 실패하도록 함
resolve(data); // 성공
} else {
reject("비동기 작업 실패"); // 실패
}
}, 1);
});
}
- 콘솔 확인 화면 : 의도적으로 비동기 작업을 실패하여 실패 반환 값을 리턴 받는 경우