Firefox 가 Async/Await 를 제공합니다

비동기 코드를 보다 간결하고, 분명하고, 관리하기 쉽게 만들어 주는 새로운 키워드, asyncawait 가 Firefox 52 에 도입되었습니다. 최신 개발자 에디션에서 사용 가능합니다. Firefox 52 는 2017년 3월 일반에게 공개될 예정입니다.

JavaScript 의 탁월한 싱글-쓰래드 퍼포먼스와 웹 응답성은 JavaScript 의 철저한 비동기적 설계 덕분입니다. 하지만, 이 비동기적 설계 때문에 “콜백 지옥 (callback hell)” 문제도 감수해야 합니다. 여러 개의 비동기 함수를 연달아 호출하자면 아주 깊이 들여쓰기(nested) 된, 관리하기 힘든 코드를 작성해야 합니다. 약간 억지스럽지만 localforage 라이브러리를 사용하는 다음 예제 코드를 보세요.

function foo(callback) {
  localforage.setItem('x',  Math.random(), function(err) {
    if (err) {
      console.error("Something went wrong:", err);
    } else {
      localforage.getItem('x', function(err, value) {
        if (err) {
          console.error("Something went wrong:", err);
        } else {
          console.log("The random number is:", value);
        }

        if (callback) {
          callback();
        }
      });
    }
  });
}

foo(function() { console.log("Done!"); });

만약 이 예제 코드 앞에서 머뭇거려진다거나 무엇을 하는 코드인지 즉시 이해할 수 없다면, 바로 그것이 문제입니다.

ES2015 는 Promise 로 비동기 함수 호출을 체인(chain)으로 묶어 처리하게 함으로써 이 문제에 대한 해결책을 제시했습니다. Promise 는, 도입 이래로, fetch 그리고 service worker 와 함께 새로운 웹 표준의 뗄 수 없는 한 부분이 되었습니다. Promise 를 사용하면 위의 예제 코드를 다음과 같이 바꿀 수 있습니다.

function foo() {
  return localforage.setItem('x', Math.random())
         .then(() => localforage.getItem('x'))
         .then((value) => console.log("The random number is:", value))
         .catch((err) => console.error("Something went wrong:", err));
}

foo().then(() => console.log("Done!"));

Promise 덕분에 함수를 호출할 때마다 코드를 깊이 들여쓰기 해가며 작성하지 않아도 됩니다. 또 에러 처리 코드도 체인(chain)의 마지막에 모아서 처리할 수 있습니다.

위의 예제 코드에서 foo() 함수가 localforage 가 동작하기 전에 바로 리턴되버린다는 사실에 주의하세요. foo() 함수 역시 Promise 를 리턴하기 때문에, foo() 함수가 처리되고 난 다음에 호출될 콜백들의 순서도 .then() 메소드로 정리할 수 있습니다.

의미론적으로, 위의 예제 코드가 하는 일는 아주 단순합니다. 하지만 문법적으로, 위의 예제 코드는 읽기 어렵고 이해하기 어렵습니다. 새로운 asyncawait 키워드는 Promise 를 더욱 사용하기 쉽게 만들기 위해 Promise 에 입힌 문법적 사탕발림 (syntactic sugar) 입니다.

async function foo() {
  try {
    await localforage.setItem('x', Math.random());
    let value = await localforage.getItem('x');
    console.log("The random number is:", value);
  } catch (err) {
    console.error("Something went wrong:", err);
  }
}

foo().then(() => console.log("Done!"));

위의 코드는 지금까지의 예제 코드와 똑같이 동작합니다. 하지만 이 코드가 훨씬 이해하기 쉽고 관리하기 쉽습니다. 우리에게 익숙한 일반적인 동기 함수 코드와 닮았기 대문입니다.

async 라고 표시된 함수는 언제나 Promise 를 리턴합니다. 따라서 리턴 값에 부여된 .then() 메소드를 호출해서 콜백의 호출 순서를 정리할 수 있습니다. await 가 앞에 붙은 구문은 해당 구문이 결정(resolve)될 때까지 함수를 정지시킵니다. 만약 await 가 표시된 구문을 처리하는 도중에 에러가 발생하면, catch 블럭이 실행됩니다. 적절한 catch 블럭이 없을 경우 리턴된 Promise 는 거절 상태(rejected state)가 됩니다.

비슷하게, 에러 처리를 async 함수 안에서 하는 대신, 다른 일반적인 코드처럼 리턴 값에 부여된 .catch() 메소드를 사용해도 됩니다.

async function foo() {
    await localforage.setItem('x', Math.random());
    let value = await localforage.getItem('x');
    console.log("The random number is:", value);
}

foo().catch(err => console.error("Something went wrong:", err))
     .then(() => console.log("Done!"));

좀더 실용적인 예제로, 사용자가 web push notification 등록을 철회(unsubscribe) 할 때 사용할 함수를 생각해봅시다.

function unsubscribe() {
  return navigator.serviceWorker.ready
         .then(reg => reg.pushManager.getSubscription())
         .then(subscription => subscription.unsubscribe())
         .then(success => {
           if (!success) {
             throw "unsubscribe not successful";
           }
         });
}

asyncawait 를 사용하면, 이 코드는 다음과 같이 됩니다.

async function unsubscribe() {
  let reg = await navigator.serviceWorker.ready;
  let subscription = await reg.pushManager.getSubscription();
  let success = await subscription.unsubscribe();
  if (!success) {
    throw "unsubscribe not successful";
  }
}

두 함수는 똑같습니다. 하지만 두 번째 코드는 Promise 로 인한 번거로움을 감추고 있습니다. 그래서 비동기 코드를 동기 코드처럼 읽을 수 있게 (물론 실행할 수도 있게) 만듭니다. 함수의 실행은 위에서 아래로, 각 라인이 완전히 결정(resolve)된 다음에, 다음 라인으로 이동합니다.

asyncawait 키워드에 대한 브라우저들의 지원 상황은 아직 충분하지 못합니다. 하지만 Babel 같은 JavaScript 트랜스파일러를 사용하면 지금 당장 사용할 수 있습니다. JavaScript 트랜스파일러는 async / await 를 기능적으로 동등한 하위 호환되는 코드로 바꿔줍니다.

asyncawait 키워드에 대해 더 알고 싶거나, Promise 에 대한 전반적인 지식이 필요하다면, 다음 자료들을 찾아보세요.

잊지 마세요. asyncawait 는 단지 Promise 를 쓰기 쉽게 만들어주는 역할을 할 뿐입니다. 우리는 양쪽 문법을 섞어 쓸 수 있습니다. Promise 에 관해 우리가 알고 있는 사항들은 asyncawait 에도 그대로 적용됩니다.

이번 기사의 샘플 코드를 개선할 수 있도록 의견을 준 Jamund Ferguson 에게 특별한 감사를 전합니다.

이 글은 Dan Callahan 이 쓴 Async/Await Arrive in Firefox 의 한국어 번역본입니다.

작성자: ingeeKim

"누구에게나 평등하고 자유로운 웹"에 공감하는 직장인.

ingeeKim가 작성한 문서들…


댓글이 없습니다.

댓글 쓰기