이 글은 개인적인 공부의 목적으로 작성하는 글이므로 오류가 있을 수 있으니 참고해주시기 바랍니다!
지난 [JavaScript] 비동기처리와 프로미스(Promise) 객체 포스팅에 이어 마지막 비동기 처리 프로세스인 async/await 관련 글을 작성하고자 한다.
async/await은 완전히 새롭게 생겨난 개념이 아닌 기존의 Promise 객체를 기반으로 조금 더 코드를 간결하고, 동기적으로 실행되는 것처럼 보이게 하는 문법적 설탕(syntatic sugar)이다.
프로미스 체이닝을 하다보면 이전에 콜백함수 중첩 사용으로 콜백지옥이 발생되는 것과 유사하게 코드가 난잡해지는 경우가 있는데 그 점을 보완하기 위해 async/await이 등장했다고 한다.
그렇다고 하여 무조건 Promise를 사용하지 않고 async/await를 사용해야 하는 것이 아니라 각각 사용해야 할 적합한 상황이 있다.
function myFunc() {
return "myFunc";
}
async function myAsync() {
return "myAsync";
}
console.log(myFunc()); // myFunc
console.log(myAsync()); // Promise {<fulfilled>: "myAsync"}
언뜻 보면 일반적인 리턴값을 가지는 함수로 보이지만, 두 번째 함수는 함수 앞에 async를 붙인 채로 선언되었고, 콘솔 출력 값을 보면 두 번째 함수는 Promise 객체를 리턴하는 것을 알 수 있다.
즉, 명시적으로 new Promise 메소드를 이용하지 않고 함수 앞에 async의 키워드만 붙이게 되면 new Promise 메소드를 이용한 것과 동일한 Promise 객체가 리턴된다는 것을 알 수 있다.
그렇다면 then 메소드도 사용할 수 있지 않을까?
async function myAsync() {
return "myAsync"
}
myAsync().then((result) => {
console.log(result); // myAsync
})
async 키워드를 이용하여 생성된 Promise 객체에도 then 메소드가 사용 가능했다. 또한 myAsync 함수에서의 return값이 then 메소드의 콜백함수 형태의 인자로 전달되는 것으로 보아 결국 Promise 객체의 resolve 함수가 호출되는 것과 동일한 역할을 하는 것을 알 수 있다.
function delayPromise(sec) {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(new Date().toISOString());
},sec * 1000);
});
}
async function myAsync() {
delayPromise(3).then((time) => {
console.log(time); // 2) 1)출력 이후 3초 뒤에 시간 출력
});
return "myAsync"
}
myAsync().then((result) => {
console.log(result); // 1) myAsync가 먼저 콘솔에 출력
});
myAsync 함수 내부에 delayPromise 함수를 이용하여 Promise 객체를 리턴하는 비동기 코드를 작성했다. 이렇게 되면 1)번의 동기적 코드인 myAsync가 먼저 콘솔창에 출력이 되고 그 다음에 2)번의 3초 뒤에 시간이 출력되는 비동기 코드가 실행된다.
이때 비동기 코드인 delayPromise의 resolve함수가 호출되기까지 기다린 후에 'myAsync'가 출력되도록 하려면 어떻게 해야할까?
바로 async 키워드를 붙인 함수 내부에 작성한 비동기 코드 앞에 await 키워드를 입력해주는 것이다.
async function myAsync() {
await delayPromise(3).then((time) => {
console.log(time); // 3초 뒤 시간 출력
});
return "myAsync"
}
myAsync().then((result) => {
console.log(result); // 3초 뒤 시간 출력과 동시에 myAsync 출력(즉 promise의 resolve를 나중에 실행하는 것)
})
즉 await는 단어 그대로 Promise 비동기 코드가 전부 실행될 때까지 '기다린' 후 그 다음 코드로 넘어가는 것이다. (이 await은 함수 앞에 async가 붙은 경우에만 사용할 수 있는 것에 주의하자!)
그렇다면 async에서의 에러 처리는 Promise 객체에서의 에러처리와 어떻게 다를까?
async function myAsyncFunc(){
throw 'myAsyncError';
}
function myPromiseFunc(){
return new Promise((resolve,reject) => {
reject('myPromiseError');
});
}
myAsyncFunc().catch((err) => console.log(err)); // myAsyncError 출력
myPromiseFunc().catch((err) => console.log(err)); // myPromiseError 출력
첫 번째 async 키워드가 붙은 myAsyncFunc 함수와 두 번째 myPromiseFunc 함수는 모두 Promise 객체를 리턴하지만, 예외 처리 방법은 조금 다르다.
async키워드가 붙은 myAsyncFunc 함수는, 일반적인 함수에서 예외처리를 하는 것과 마찬가지로 throw를 이용하여 에러를 처리한다.
아래는 프로미스의 결과값을 기다리는 await 기능이 추가된 코드이다.
function wait(sec) {
return new Promise(resolve => {
setTimeout(() => {
resolve('done!');
}, sec * 1000);
});
}
async function myAsyncFunc() {
console.log(new Date());
await wait(3);
console.log(new Date());
}
const result = myAsyncFunc();
async 키워드가 붙은 myAsyncFunc 함수 안에 Promise를 리턴하는 wait 함수를 호출했다. 여기서 await 키워드가 없을 때는 비동기코드이므로 두 개의 new Date()의 값이 시간 차 없이 그대로 콘솔 창에 출력되었지만, await 키워드를 붙이고 나서 첫 번째 new Date() 값 출력 후 3초 뒤에 두 번째 new Date()의 값이 콘솔에 출력된다.
이렇게 resolve 호출로 결과값을 리턴 할 때는 문제없이 진행이 되지만, 만약 wait함수 내부에 resolve가 아닌 reject 함수가 호출되어 에러 처리를 해야 할 땐 어떻게 코드를 작성할까?
그럴 때 예외를 처리하는 방법은 try ~ catch이다. Promise에서 예외 처리를 위해 catch 메서드를 이용했던 것처럼 async에서는 catch{error}를 이용하면 된다.
function wait(sec) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('rejected');
}, sec * 1000);
});
}
async function myAsyncFunc() {
console.log(new Date());
try {
await wait(3);
} catch(e) {
console.error(e);
}
console.log(new Date());
}
const result = myAsyncFunc();
이 때 콘솔 창의 출력 값은 아래와 같다.
여기까지 비동기 작업의 동기적 처리에 대한 기초적인 개념은 공부된 것 같지만 실제 실무에서 어떻게 사용될지는 추가적으로 더 공부가 필요한 것 같다.😂