DEV Community

loading...

async/await 구문 작성 후 무야호를 외치는 방법

Ukjin Yang
I'm a freelance full stack developer, from hell.
・1 min read

팁 쓰려고 하는데 제목 때문에 시간 잡아먹기 싫어서 어그로 좀 끌었다.

요즘 한국에서도 async 구문과 await 구문을 쓰면서 자바스크립트 비동기 구문에 무야호~를 외치는 사람들이 늘어나기 시작했다.
콜백 지옥에서 벗어나서 접근하기 어려운 Promise 객체를 async/await 구문으로 해결하는 기쁨은 말로 표현할 수 없을 것이다. 비동기를 동기처럼 작성할 수 있다는 게 얼마나 기쁜 일인가.

이제 이 구문의 근간이 되는 코루틴(Coroutine)에 대한 개념을 익히면 더 좋겠지만 이건 잠깐 뒤로 미루고, async/await 구문을 잘 작성해서 진정한 무야호~를 외치는 방법을 오늘 다루고자 한다.

await variable/const/expression

대부분은 Promise 객체를 내뱉는 함수를 await 뒤에 넣을 것이다.

async function doSome() {
  const result = await muyaho('api');
  // 무야호~
}
Enter fullscreen mode Exit fullscreen mode

그 어느 스터디, 튜토리얼, 팁을 가도 다 이런 식으로 예제를 뿌리고 가르칠 것이다.
하지만 Promise 객체라면 꼭 함수가 아니라도 await 구문을 넣을 수 있다는 점을 잊지 말자.

async function doSome() {
  const waiting = muyaho('api');
  // 딴짓하기
  const result = await waiting;
  // 무야호~
}
Enter fullscreen mode Exit fullscreen mode

이는 흔한 케이스는 아니지만, 부가적으로 가져올 게 있다거나, 벤치마크를 실시하여 순수 비동기 소요 시간을 계산한다던가 등에 쓰임새는 있다.
물론 Promise 객체가 아닌 식 및 변수라도 await 구문은 다 받아들이며, 당연하겠지만 Promise 객체가 아니면 모두 Promise.resolve() 정적 메소드의 재물이 될 것이다. 즉, 즉시 완료된 결과가 도출된다는 얘기. 이것도 당연히 알고 있을 테고.
이를 이용해 비동기 처리를 순차적으로가 아닌 동시에 하는 것도 가능하다.

async function doSomeMore() {
  const waiting1 = muyaho('api1');
  const waiting2 = muyaho('api2');
  // 딴짓하기
  const result1 = await waiting1;
  const result2 = await waiting2;
  // 무야호~
}
Enter fullscreen mode Exit fullscreen mode

이렇게 하면 비동기 함수 muyahoPromise 객체 생성하자마자 역할을 끝내 waiting1 변수의 비동기 결과를 가져오기 전에 waiting2 변수를 가져올 것이다. 물론 Promise 객체로.

이렇게 하면 Promise.all 같이 동시에 복수 비동기 처리를 어렵지 않게 처리할 수 있다.

그런데!

이런 패턴을 쓰기 전에 반드시 짚고 넘어가야 하고, 이번 글의 메일 디쉬이기도 하다. 이 메인 디쉬를 잘 소화한 개발자들은 이제 유연하고 강력한 코루틴의 세계에 흠뻑 빠지며 무야호를 외치면 된다.

있었는데 없어진 코루틴 오류

비동기 함수를 사용하면서 예외 처리도 확 편해졌다. 동기식 때처럼 try/catch 문을 사용하면 되기 때문이다.

흔치 않은 케이스지만, 비동기 함수를 호출했고 await 구문을 아직 쓰지 않았는데 오류가 났다면 당신은 어떻게 대응할 것인가?

자, 여기 테스트 코드를 주도록 하겠다.

// 비동기 딜레이 시뮬레이션용
function delay(fn, time) {
  return new Promise((A,B) => setTimeout(() => {try {A(fn())} catch(e) {B(e)}}, time))
}
// 본격 비동기
(async () => {
  console.log('async prepare!')
  const a = delay(() => console.log('success!'), 1000);
  const b = delay(() => {throw new Error('failed!')}, 500);
  console.log('async ready!')
  try {
    const c = await a;
    const d = await b;
    console.log('async finish!')
  } catch(e) {
    console.log('async failed!')
    console.error(e)
  }
})();
Enter fullscreen mode Exit fullscreen mode

여기서 두 개의 비동기 객체를 가져왔는데, 하나는 성공, 하나는 실패를 가져온다.
위 코드를 실행할 경우 개발자 도구의 콘솔 창에서는 이런 식으로 흐른다.

개발자 도구 콘솔 화면

만약 파란 글자로 된 오류 메시지가 안보인다면, 콘솔에서 Level 선택란에 Verbose 항목에 체크하거나, All levels 를 체크하면 된다.
메시지 흐름을 보면 이미 0.5초 뒤 오류를 뿜었는데, 비동기 오류를 뿜는 비동기 함수 수행 후에도 그 다음 구문이 동작한 것을 확인할 수 있다.
그러다가 await로 결과를 가져오려 하면, 해당 비동기 루틴은 이미 오류가 났기 때문에 더 이상의 구문은 수행되지 않고 오류를 뿜어내 catch 문에 잡힌 것을 볼 수 있다.

여기서 재밌는 사실이 있다면, 실행한 뒤 0.5초 시점에 이미 오류가 났다가, 1초에 재대로 오류가 났다는 것이다. 그러면서 실패 메시지가 출력되었고, 이미 출력된 오류 메시지는 갑자기 사라지면서 verbose 레벨, 즉, debug 로그 레벨로 갑자기 격하되었다는 사실이다. 왜 이렇게 했는지 까지는 안 알아봤으니 누가 알려주면 감사. 그냥 크로미움 등의 브라우저 엔진에서 비동기 오류 발생이 이미 했다 해도 나중에라도 catch 이벤트나 async catch 구문 걸면 caught 처리 되어 기존 uncaught error 로그가 debug 레벨로 격하된다고 한다. 개발자에게 혼란을 덜 주기 위한 친절한 배려가 아닐 수 없다.(출처댓글)

내가 왜 이렇게 코드를 만들었냐면, await 전에 비동기 루틴은 오류가 나던 안나던 계속 비동기 함수는 지 할 일 한다는 뜻을 전달하기 위함이다. 그러다 await 로 비동기 결과를 가져오려는 순간, 이미 오류가 났기에 메인 비동기 함수는 오류가 났다는 것.
이렇듯 시점이 햇갈리는 비동기 오류가 발생할 수 있으므로,
이를 잘 컨트롤 하는 것이 자바스크립트 코루틴을 잘 다루느냐 못 다루느냐가 결정되는 중요한 요소라는 것이다.

간단히 말하자면, 비동기 오류는 언제든 일어날 수 있으며, 일찍이 잡던 늦게 잡던 잡을 수 있고, 이를 대응할 수 있다는 가정을 가지고 그냥 내가 의도적으로 짠 코드라고 보면 된다. 우연은 없다. 비동기에 익숙하다면 그 뜻을 알리라.

물론 await muyaho() 같이 바로 비동기 함수를 실행하고 결과를 가져오는 케이스가 대부분이긴 하지만, 동시성 제어를 고려한다면, 여기서 예기치 못한 오류가 났을 때, 잘 대응하는 방법을 고민해 볼 기회를 주기 위해서다.

이 오류 시점과 오류를 catch 하는 시점에 대한 대응이 가능하다면, 당신은 이제 코루틴을 쓰면서 비동기에게 무야호를 시전하면 된다.

끗.

Discussion (0)