티스토리 뷰
비동기처리에서 쓰는 Promise, async-await에 대해 배웠다. 어려운 것 같기도 하고, 생각보다는 이해가 가는 것 같기도 하다. 하지만 응용했을 때가 문제일 테니 우선 기초를 잘 알아둬야겠다.
callback hell (콜백지옥)
콜백 함수를 쓰다보면 콜백 안에 콜백, 그 안에 또 콜백, 또 콜백... 마치 무한루프처럼 콜백이 계속해서 들어갈 때가 있다. 이것은 콜백 함수가 비동기 처리를 위해 쓰는 것이라 그런데, 일단 한 번 비동기 처리를 했을 때 그 비동기 처리가 끝난 후 다른 처리가 하고 싶다면 내부에 집어넣어야 한다. 그 때문에 콜백 함수 안에 계속해서 콜백 함수가 들어가는 것이다.
이것을 callback hell, 흔히 콜백 지옥이라고 한다.
Promise
Promise는 바로 이런 비동기 처리를 관리하기 위해 만들어졌다. 비동기 처리가 완료된 이후에 다음 작업을 연결시켜 진행할 수 있다. 작업 결과에 따라 성공, 또는 실패를 리턴하며, 결과 값을 전달 받을 수 있다. Promise는 다음과 같이 사용한다.
const promise1 = new Promise((resolve, reject) => {
// 비동기 작업
});
Promise는 생성자 함수로 파라미터 두 개를 받는다. 각각 resolve와 reject이다. 이것도 각각의 함수이다.
만약 resolve를 호출하게 된다면 "비동기 작업이 성공" 했음을 의미한다.
reject를 호출하게 된다면 반대이다. "비동기 작업이 실패" 했음을 의미한다.
그럼 이것들은 어떻게 받을 수 있을까? 바로 then과 catch 메서드를 사용한다.
then은 해당 Promise가 성공했을 때의 동작을 지정한다. 파라미터로 함수를 받는다.
catch는 해당 Promise가 실패했을 때의 동작을 지정한다. 파라미터로 함수를 받는다.
다음과 같은 예시를 들어보자.
const promise1 = new Promise((resolve, reject) => {
resolve();
});
promise1.then(()=>{
console.log("성공!");
}).catch(()=>{
console.log("실패!");
});
위 Promise에서는 바로 resolve가 호출되었으므로, 성공으로 간주하여 then이 실행된다. catch는 건너뛴다. 만약 resolve가 아니라 reject를 바로 호출했다면 catch가 실행되어 "실패!"가 출력됐을 것이다.
그렇다면 비동기 작업을 수행할 때마다 새로운 Promise 객체를 만들어야 할까?
그러지 않아도 된다. 그냥 Promise 객체를 리턴하는 함수를 만들어서 사용하면 된다. 좀 더 응용해보자.
function random(n1, n2){
return parseInt(Math.random()*(n2-n1+1)+n1);
}
function getLuckyResult(){
return new Promise(function(resolve, reject){
setTimeout(()=>{
console.log("당신의 추첨 결과는...?");
const lucky = random(1, 9);
if(lucky%2==0){
resolve({msg: "당첨입니다!", a: 1, b: 2, c: 3});
} else {
reject({msg: "꽝!!!", d: -1, e: -2});
}
}, 1000);
});
}
const mypromise = getLuckyResult();
mypromise.then(({msg, a, b, c})=>{
console.log("%s, a=%d, b=%d, c=%d", msg, a, b, c);
}).catch(({msg, d, e})=>{
console.error("%s, d=%d, e=%d", msg, d, e);
}).finally(()=>{
console.log("fin :)");
});
getLuckyResult 함수는 random 함수로 1~9까지의 숫자를 랜덤으로 하나 뽑는다. 그리고 그 숫자가 2의 배수라면 성공으로 resolve를 호출하고, 아니라면 실패로 reject를 호출한다.
그리고 이 getLuckyResult 함수를 mypromise라는 변수에 호출한다. 그러면 함수 내부에서 Promise 객체가 생성되며 생성자 파라미터가 실행된다.
그 후, then과 catch를 이용하여 비동기 작업의 결과를 알아낸다. then과 catch를 이용하면, 비동기 작업 이후에 후속처리도 가능하다. finally는 성공과 실패 여부에 상과 ㄴ없이 무조건 실행되는 마무리 처리이다. 생략이 가능하다.
위의 예제에서 then과 catch가 받고 있는 함수는 각각 파라미터를 하나씩 갖고 있다. then은 이 파라미터에 resolve의 값을 할당받는다. 즉, 예제에서는 {msg: "당첨입니다!", a: 1, b: 2, c: 3}이다. catxh는 이 파라미터에 reject의 값을 할당받는다. 예제에서는 {msg: "꽝!!!", d: -1, e: -2}이다. 파라미터가 JSON 형태인 것은, 바로 구조분해를 하기 위해서다. 구조분해를 하지 않고 변수 하나만 넣어도 괜찮다.
resolve와 reject의 파라미터는 하나만 가능하므로, 여러 개의 정보를 보내야 하는 경우에는 JSON 구조로 보낸다.
위의 예제에서 lucky 값이 짝수라면 then이 실행되어 "당첨입니다! a=1, b=2, c=3"이 출력된다.
lucky가 홀수라면 catch가 실행되어 "꽝!!! d=-1, e=-2"가 출력된다.
예제를 보면 이런 구조로 되어있다. mypromise.then().catch()
이렇게 연결된 것을 체인이라고 부른다. Promise를 사용하면 이러한 체인을 사용할 수 있다.
async-await
async-await는 조금 더 문법을 간단하게 만들 수 있도록 도와준다.
사용 방법은 간단하다. function 앞에 async 키워드를 붙여주면 된다. 이렇게 만들어진 async 함수에서는 Promise가 아닌 값을 리턴하더라도 Promise가 반환된다. 즉, Promise를 리턴하는 과정을 거칠 필요가 없다.
await는 Promise가 완료될 때까지 기다린다. 그리고 Promise가 resolve한 값을 내놓는다. 즉, 성공할 때의 값을 내놓는다. 만약 Promise에서 reject가 발생했다면 예외가 발생하므로 예외처리를 해주어야 한다.
위의 예제를 async-await 형식으로 바꿔보자.
function random(n1, n2){
return parseInt(Math.random()*(n2-n1+1)+n1);
}
function getLuckyResult(){
return new Promise(function(resolve, reject){
setTimeout(()=>{
console.log("당신의 추첨 결과는...?");
const lucky = random(1, 9);
if(lucky%2==0){
resolve({msg: "당첨입니다!", a: 1, b: 2, c: 3});
} else {
reject({msg: "꽝!!!", d: -1, e: -2});
}
}, 1000);
});
}
(async () => {
let reult = null;
try{
result = await getLuckyResult();
console.log("%s, a=%d, b=%d, c=%d", result.msg, result.a, result.b, result.c);
} catch(e) {
console.error("%s, d=%d, e=%d", e.msg, e.d, e.d);
} finally {
console.log("fin!!");
}
})();
위에서, Promise를 리턴하는 함수를 호출하기 위해 async 함수를 정의했다.
함수 전체를 괄호()로 묶고 바로 실행(); 하면 즉시 실행할 수 있다. 이것을 즉시 실행 함수라고 부른다. async 함수는 주로 이런 즉시 실행 함수로 정의된다.
try-catch 예외처리를 적용한다. await를 사용하여 함수를 호출한다. 함수에서 resolve가 호출된다면(성공했다면) 전달된 파라미터를 리턴한다. 만약 reject가 호출된다면(실패했다면) 예외가 발생하므로 catch문으로 넘어간다. 전달된 파라미터는 catch의 파라미터인 e로 전달된다.
따라서, 위의 예제에서 lucky가 짝수라면 resolve가 호출되어 await는 정상적으로 작동하고, 전달한 파라미터인 {msg: "당첨입니다!", a: 1, b: 2, c: 3}가 변수 result로 리턴된다. 그리고 console.log문에 의해 출력된다.
lucky가 홀수라면 예외가 발생한다. 그대로 catch문으로 넘어가서 파라미터 e에 {msg: "꽝!!!", d: -1, e: -2}가 전달된다. 그리고 console.log문에 의해 출력된다.
finally는 성공과 실패 여부에 상관 없이 "fin!!"을 출력한다.
이렇게만 보면 그리 어려운 것 같지 않은데, 위에서 말했듯이 응용하게 되면 어려울 것 같다. 응용하게 된다면 다시 부딪혀 봐야겠다.
'javascript' 카테고리의 다른 글
12. 이벤트 (0) | 2022.09.08 |
---|---|
11. 웹 페이지와 Javascript (0) | 2022.09.07 |
9. 예외처리 (0) | 2022.09.05 |
8. 내장기능 (0) | 2022.09.01 |
7. 모듈 (0) | 2022.08.31 |