ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 비동기 프로그래밍에 관하여 - 3. async / await
    front-end/ES6 2022. 2. 21. 10:23
    728x90

    Promise를 사용하면 체이닝을 통해 순차적으로 코드를 수행할 수 있다.

     

    다만, 체이닝이 길어지고 복잡해진다면 코드 가독성이 떨어질 수 있다.

     

     

     

    async와 await를 사용하면 마치 동기식 코드를 짜는거 마냥 간편하게 코드를 작성할 수 있다.

     

    async / await은 새로운 기능을 추가하는 것이 아니라 기존의 promise에서 간편한 api를 제공한다.

     

    이와 같이 기존에 존재하던 기능 위에 편리한 기능을 감싸서 사용하는 것을 syntactic sugar라고 한다.

     

    사람이 프로그래밍 언어를 조금 더 '달콤하게'

    이해하기 쉽고 간결하게, 명확하게 도와주는 문법이라고 생각하면 될 것같다.

     

     

    async와 & await은 promise를 보다 깔끔하게 사용할 수 있는 방법이다.

     

    그렇다고해서 promise가 나쁘다거나 async & await로 대체해서 사용해야하는 것이 아니다.

     

     

     

     

    function fetchUser() {
      return new Promise((resolve, reject) => {
        //do network request in 10 secs...
        resolve("username");
      });
    }
    
    const user = fetchUser();
    user.then(console.log);

     

    위와 같은 비동기적 실행을 위한 코드가 있을 때, 이 코드를 수정하여보자.

     

     

    async function fetchUser() {
      return 'username'
    }

    자신이 비동기적으로 처리할 함수에 'async'키워드를 붙혀주면 자동적으로 Promise로 변환이 된다.

     

    똑같이 Promise 객체를 반환한다.

     

     

    만약 async 키워드로 만들어진 함수 내부에서 동기적인 코드를 작성하고 싶다면 await 키워드를 사용하면 된다.

     

    function delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }
    
    async function getSomething1() {
      await delay(3000);
      return "something1";
    }
    
    async function getSomething2() {
      await delay(3000);
      return "something2";
    }

     

    await는 콜백지옥을 해결하는데에도 도움이 된다.

     

    function delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }
    
    async function getSomething1() {
      await delay(3000);
      return "something1";
    }
    
    async function getSomething2() {
      await delay(3000);
      return "something2";
    }
    
    function getAll() {
      return getSomething1.then((one) => {
        return getSomething2.then((two) => `${one} + ${two}`);
      });
    }
    
    getAll.then(console.log);

     

    위와 같이 promise끼리 콜백으로 물려있는 상황이 있을 수 있다.

     

    function delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }
    
    async function getSomething1() {
      await delay(3000);
      return "something1";
    }
    
    async function getSomething2() {
      await delay(3000);
      return "something2";
    }
    
    async function getAll() {
      const one = await getSomething1();
      const two = await getSomething2();
      return `${one} + ${two}`;
    }
    
    getAll.then(console.log);

     

     

    async와 await 키워드만 사용했을 뿐인데, 우리가 익숙한 느낌으로, 마치 동기적인 코드를 작성할 수 있다.

     

    그런데 위의 코드를 살펴보면 큰 문제점이 하나 있다.

     

    바로 비동기적인 실행들이 모두 연결되어 있어, 병렬적 처리가 되지 않는다는 것이다.

     

    getAll() 함수를 통해 결과값을 받아오기 위해서는 3000ms + 3000ms, 총 6초를 꼬박 기다리고 있어야한다.

     

    하지만, 각각의 처리가 서로를 기다려줄 필요는 없다.

     

    웹 페이지에서 서버와 통신하는 api가 늘어날 수록 페이지가 표시되기까지 걸리는 시간은 어마어마하게 늘어나버릴것이다.

     

     

     

     

    해결방안 중 하나는 임의로 Promise 객체를 만들어버리는 것이다.

     

    promise 객체에 작성되어있는 코드 excutor는 promise가 생성되자마자 실행되는 특징이 있다.

     

     

    function delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }
    
    async function getSomething1() {
      await delay(3000);
      return "something1";
    }
    
    async function getSomething2() {
      await delay(3000);
      return "something2";
    }
    
    async function getAll() {
      const promise1 = getSomething1();
      const promise2 = getSomething2();
    
      const one = await promise1;
      const two = await promise2;
      return `${one} + ${two}`;
    }
    
    getAll().then(console.log);

    병렬적으로 코드가 실행되어 대략 3000ms가 걸렸다.

     

    하지만 비동기적인 코드가 늘어날수록 쓸데없이 promise객체를 생성하는 지저분한 코드가 될 것이다.

     

    그렇기에 promise에서는 이를 해결하기 위한 all() 메서드를 제공한다.

     

    각각의 promise 값들을 배열으로 묶어서 내보낸다.

     

     

    async function getAll() {
      const promise1 = getSomething1();
      const promise2 = getSomething2();
    
      const one = await promise1;
      const two = await promise2;
      return `${one} + ${two}`;
    }
    getAll().then(console.log);
    /////////////////////////////////////////////
    
    function getAll2() {
      return Promise.all([getSomething1(), getSomething2()]).then((result) =>
        result.join(" + ")
      );
    }
    getAll2().then(console.log);

     

    getAll()과 getAll2()는 동일한 결과를 반환한다.

     

     

     

    병렬적인 처리를 진행하면서 가장 처음으로 완료되는 하나의 결과만 반환받을수도 있다.

     

    Promise.race() 메서드를 사용하면 된다.

     

    function getOnlyOne() {
        return Promise.race([getSomething1(), getSomething2()]);
    }
    getOnlyOne().then(console.log);

     

     

    지난 시간 Promise의 체이닝을 통해 작성한 코드가 있었다.

     

    class UserStorage {
      loginUser(id, password) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (
              (id === "user" && password === "user") ||
              (id === "admin" && password === "admin")
            ) {
              resolve(id);
            } else {
              reject(new Error("not found"));
            }
          }, 2000);
        });
      }
    
      getRoles(user) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (user === "admin") {
              resolve({ name: "admin", role: "admin" });
            } else {
              reject(new Error("no access"));
            }
          }, 1000);
        });
      }
    }
    
    const userStorage = new UserStorage();
    const id = prompt("enter user id");
    const password = prompt("enter user password");
    
    userStorage
      .loginUser(id, password)
      .then(userStorage.getRoles)
      .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`))
      .catch(console.log);

     

    위의 코드를 async와 await를 활용하여 수정하면 다음과 같다. (맨 아래부분 실행 로직만 변경시키겠다.)

     

    const userStorage = new UserStorage();
    const id = prompt("enter user id");
    const password = prompt("enter user password");
    
    //1. Promise
    userStorage
      .loginUser(id, password)
      .then(userStorage.getRoles)
      .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`))
      .catch(console.log);
    
    //2. async & await  
    async function checkUser() {
      try {
        const userId = await userStorage.loginUser(id, password);
        const user = await userStorage.getRoles(userId);
        alert(`hello ${user.name}, you have a ${user.role} role`);
      } catch (error) {
        console.log(error);
      }
    }
    728x90

    댓글

Designed by Tistory.