async, await를 작성해보는데 처음부터 에러를 만났다. 그 후 완전히 잘못 알고 있다는 사실을 알게 된 뒤 자세히 공부하게 됐다. 좀 찾아보니 async와 await는 Promise를 편리하게 사용할 수 있는 문법이었다. 그렇기 때문에 Promise에 대해서도 잘 알고 있어야지 async, await, Promise를 이용해 효율적인 비동기 프로그래밍을 구현할 수 있다.
async await를 잘 알지 못하던 시절... await 한번 써봐야지 하고 비동기가 await에 들어가면 되겠지? 라고 아래의 코드를 작성했다. 근데?! 뭐지?
function delay() {
setTimeout(() => {
return 1;
}, 1000);
}
async function test() {
return await delay();
}
test().then((k) => {
console.log(k + 1); //NaN
});
안 나온다. 마냔 k가 undefined 일뿐이다. 이유는 정말 간단했다.
await는 Promise를 위한 문법이었다. Promise를 반환할 때만 사용 가능한 것이었다. 음... 그럼 await를 사용하던 fetch메소드는???
그렇다 fetch도 Promise를 반환한다.
간단하게 await는 Promise 객체의 resolve를 기다려주는 아이였다.
이런 await를 사용할 때는 async 함수 안에서만 사용해야 한다. 즉, await 쓰고 싶으면 async를 써줘야 한다.
그러면 async는 뭘까?
간단하게 async함수는 Promise 객체를 리턴해준다.
await와 함께 쓰인 async 함수는 비동기 함수가 된다. (이브 블로그를 보고 async함수에 대해 많은 것을 알아서 자세하게 작성할 수 있게 됐다.🙇♂️) async함수의 흐름을 정확하게 알아야 될 필요가 있다.
정확하게는 await를 만나는 뒤의 코드부터
이 말은 async함수도 then과 함께 사용할 수 있다는 뜻이다. 아래의 코드를 보고 흐름을 이해해 보자.
async function test() {
console.log('start test Function');
const res = await promiseFn();
console.log(res);
console.log('finish test Function');
}
function promiseFn() {
return new Promise((resolve) => {
resolve('promise Function finished');
});
}
console.log('hello');
test();
console.log('bye');
내 처음 예상은 음~~ test는 async함수이니까 hello, bye 찍히고 나머지가 순차적으로 찍히겠구나 라고 생각했다. 하지만 결과는 아래와 같이 나왔다.
hello
start test Function
bye
promise Function finished
finish test Function
async함수는 async라고 작성이 돼있다는 이유만으로 비동기로 실행 되는 것이 아니라 await을 만나는 순간부터 비동기로 진행이 된다고 생각하면 된다.
즉, test()함수가 호출
->start test Function
이 출력될 때 까지는 일반 함수처럼 진행
->promiseFn()
이 Promise{'promise Function finished'}
를 반환하고
->await을 만나는 순간 microtast Queue로 넘어가서 대기한다.
위와 같은 과정 때문에 async함수는 await을 만나는 코드부터 비동기로 실행된다고 생각하면 된다.
Promise.resolve('something') 반환
async function k (){
return 'something'
}
//=>Promise {<fulfilled>: "something"}
Promise.resolve() 반환
async function k (){
return
}
//또는
async function k (){}
//=>Promise {<fulfilled>: undefined}
Promise.reject(error) 반환
- return 하지 않거나 return 만 있을 때
```javascript
async function k (){
throw new Error('k에러!')
}
//Promise {<rejected>: Error: k에러!
// at kkk (<anonymous>:2:11)
// at <anonymous>:1:1}
try catch를 사용해서 async함수의 예외처리를 할 수 있다. 근데 일반적으로 에러처리를 하면 에러처리가 되지 않을 수 있다. 아래 코드를 확인해보자.
async function thisThrows() {
throw new Error("Thrown from thisThrows()");
}
try {
thisThrows();
} catch (e) {
console.log('에러처리!');
} finally {
console.log('We do cleanup here');
}
결과
We do cleanup here
Promise {<rejected>: Error: Thrown from thisThrows()
위 코드의 결과는 예외 처리가 안된다. 왜 그럴까? async함수에서 에러를 throw
하면서 Promise.reject(error)
가 반환되는데 코드는 기다리지 않고 catch
를 넘어 finally
까지 가서 실행되는 것이다.
(사실 바로 위에 적은 async함수 흐름과 좀 다른가? 라는 생각이 들기도 하지만 throw 에러 자체가 비동기라고 생각하고 있다. 적어도 지금까지는... 이 글을 읽으시는 분들 중 이 부분이 잘못된 정보면 꼭 댓글로 알려주시면 감사하겠습니다!)
그럼 어떻게 처리를 해야 될까? 비슷하지만 총 3가지 방법이 있다.
async function thisThrows() {
try{
throw new Error("Thrown from thisThrows()");
}catch(e){console.log('에러처리완료!')}
}
thisThrows();
//에러처리완료!
async function thisThrows() {
throw new Error('Thrown from thisThrows()');
}
async function handleError() {
try {
await thisThrows();
} catch (e) {
console.log('에러처리 완료!!')
} finally {
console.log('We do cleanup here');
}
}
handleError();
//에러처리 완료!!
Promise도 .catch
를 이용해 예외처리를 하는 것처럼 Promise.reject(error)
를 반환하는 async함수에 .catch
를 사용해주면 된다.
async function thisThrows() {
throw new Error('Thrown from thisThrows()');
}
thisThrows().catch((e) => {
console.log(e);
console.log('에러처리완료!');
});
/*
출력
Error: Thrown from thisThrows()
at thisThrows (<anonymous>:2:9)
at <anonymous>:4:1
에러처리완료!
*/
나는 아직 async 프로그래밍을 많이 해보지 않아서 잘 모르겠지만 저렇게 Error를 보여주는 .catch
방식이 좋지 않나? 라고 생각이 들긴한다.
(이 부분도 아시는 분들은 댓글을 남겨주시면 감사하겠습니다!)
그동안 정확하게 알지 못했던 async await을 간략하게나마 정리하는 시간을 가져서 다행이라는 생각이 든다.
다시 한번 더 생각이 드는 게 마냥 문법을 익히려 하는 것보다는 내가 아는 대로 작성해보고 발생하는 에러를 보며 수정해가는 과정에서 더 몰입력 있게 학습하는 모습을 볼 수 있었다.
*공부하는 과정에서 작성한 글이니 잘못된 부분을 댓글로 알려주시면 정말 감사하겠습니다!