ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • async/await을 이용한 비동기 자바스크립트
    Web/자바스크립트 2021. 2. 21. 14:57
    반응형

    Promise를 consume함으로써 비동기 자바스크립트를 다루는 것은 물론

    코드를 이전보다 훨씬 깔끔하게 만들어주는 역할을 하지만, 여전히 코드의 양이 만만치가 않다.

    조금 더 사용법을 간단히 만들 필요가 있겠다고 느꼈는지, ES8(ES2017)에서는

    async/await이라는 새로운 consume 방법을 내놓았다.

     

    참고로, async/await은 개발자들이 consume을 더 쉽게 하기 위한 문법이므로

    produce와는 전혀 관계가 없다. promise를 produce하는 방식은 이전과 동일하다.

     

    Promise에 관한 포스팅에서 다루었던 아래의 예제를 async/await을 사용해서 작성해볼 것이다.

    const getIDs = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve([523, 883, 432, 974]);
      }, 1500);
    });
    
    const getRecipe = recID => {
      return new Promise((resolve, reject) => {
        setTimeout(ID => {
          const recipe = {title: 'Fresh tomato pasta',
                          publisher: 'Jonas'};
          resolve(`${ID}: ${recipe.title}`);
        }, 1500, recID);
      });
    };
    
    const getRelated = publisher => {
      return new Promise((resolve, reject) => {
        setTimeout(pub => {
          const recipe = {title: 'Italian Pizza',
                          publisher: 'Jonas'};
          resolve(`${pub}: ${recipe.title}`);
        }, 1500, publisher);
      });
    };

    produce하는 방법은 변하지 않았으므로, produce하는 부분은 그대로 유지된다.


    먼저, 기본 개념은 'await문을 사용하기 위해서는 해당 코드가 포함된 함수가 async 함수여야 한다' 라는 것.

    async 함수로 만들어주는 방법은 굉장히 간단하다. 함수의 맨 앞에다가 async 키워드를 붙여주기만 하면 된다.

    그러면 '이 함수는 asynchronous function입니다' 라는 도장을 쾅 찍게 되는 것.

     

    그리고 이 함수는 이제 background에서 실행되는 함수가 된다.

    (This function keeps running asynchrounously in the background.)

    async function getRecipesAW() {
    }
    
    getRecipesAW();

     

    이제, 우리는 이 async 함수 내부에 한 개 이상의 await expressions를 사용할 수 있다.

    일단은 우리가 만들어뒀던 getIDs라는 promise를 consume하기 위해, 첫 번째 await 문장을 사용해보자.

    promise 앞에다가 'await'를 붙여주고, 그 결과를 변수에 할당해주도록 하자.

    async function getRecipesAW() {
      const IDs = await getIDs;
      console.log(IDs); // [523, 883, 432, 974]
    }
    
    getRecipesAW();

    어떤 식으로 진행되는지를 살짝 묘사해보자면

    일단 'await expression'이 나타난 시점에서, promise가 fulfilled 될 때까지 함수가 중단된다.

    (해당 promise가 fulfilled될 때까지 기다리게 된다)

     

    그리고나서 promise가 resolved(succesful) 되면,

    await expression(여기서는 'await getIDs') 전체의 값이 해당 promise의 결과값이 되고,

    그 값이 IDs라는 변수에 할당된다.

    (여기서는 promise가 [523, 883, 432, 974] 라는 배열값으로 resolved 될 것이고, 이 값이 IDs 변수의 값이 된다)


    이제 다른 promise들도 계속해서 consume 해보자.

    async function getRecipesAW() {
      const IDs = await getIDs;
      console.log(IDs); // [523, 883, 432, 974]
      const recipe = await getRecipe(IDs[2]);
      console.log(recipe);
    }
    
    getRecipesAW();
    

     

    이번에 사용할 getRecipe는 promise가 아니라 하나의 함수이다.

    하지만 해당 함수의 return값이 promise이기 때문에 동일하게 await을 앞에다가 사용해주면 된다.

    동일하게 해당 promise가 resolved 될 때까지 기다리게 되고,

    그 결과를 이용할 수 있다는 것을 console.log를 통해 확인한다.

     

    consume할 promise가 더 있다면, 계속해서 동일하게 await을 사용해주면 된다.

    이제 세 번째 promise를 consume할 차례.

    async function getRecipesAW() {
      const IDs = await getIDs;
      console.log(IDs); // [523, 883, 432, 974]
      const recipe = await getRecipe(IDs[2]);
      console.log(recipe); // 432: Fresh tomato pasta
      const related = await getRelated('Jonas Schmedtmann');
      console.log(related); // Jonas Schmedtmann: Italian Pizza
    }
    
    getRecipesAW();

     

    결과적으로 이전보다 훠어얼씬 간단한 코드이지만, 이전과 정확히 동일하게 동작한다.

    각각의 await 키워드에서 코드의 실행이 멈추고, 우리는 promise가 return할 때까지 기다리게 된다.

     

    중요하게 염두해둬야 할 점은, await expression은 무조건 async function 안에서만 사용될 수 있다는 점.

    그리고 async function은 background 에서 돌아간다는 점.

    (Main code는 중단되면 안되기 때문에, 매우 중요한 사실이다.)

     

    즉, main code가 중단되지 않으면서 async code가 따로 background 에서 돌아갈 수 있게 하기 위해

    async code들을 따로 async function 안에 가둬놓는 거라고 생각하면 된다.

    그렇게 하면 async function은 background 에서 따로 돌아가기 때문에

    해당 function이 멈추는 것은 아무런 문제가 되지 않는다.


    이제, 우리가 async function으로부터 특정 값을 return받고 싶다고 가정해보자.

    async function getRecipesAW() {
      const IDs = await getIDs;
      console.log(IDs); // [523, 883, 432, 974]
      const recipe = await getRecipe(IDs[2]);
      console.log(recipe); // 432: Fresh tomato pasta
      const related = await getRelated('Jonas Schmedtmann');
      console.log(related); // Jonas Schmedtmann: Italian Pizza
    
      return recipe;
    }
    
    const rec = getRecipesAW();
    console.log(rec); // Promise {<pending>}

    async function에서 값을 return한 후, 그 값을 rec 변수에 할당하여 출력하였다.

    우리는 return된 recipe 값이 출력될 거라고 기대했지만, 기대와는 달리 pending 상태의 promise가 출력되었다.

    왜 우리가 원하는 대로 작동을 하지 않는 것일까?

     

    그 이유는, 위에서도 언급했듯이

    getRecipesAW라는 async function이 background에서 asynchronous하게 실행되고 있기 때문이다.

    하지만 rec에 return값을 할당하고, console.log로 출력하는 코드는 synchronous하게 실행된다.

     

    즉, 우리가 console.log(rec)를 실행하는 시점에서는

    아직 getRecipesAW가 background에서 계속 실행중이고, 값을 return하지 않은 상태인 것.

     

    좀 더 상세하게 설명해보자면

    const rec = getRecipesAW() 라는 코드를 실행하면, 그 함수는 background에서 실행되게 놔두고

    바로 뒤이어 console.log(rec) 이라는 코드를 실행하게 된다.

    근데 그 시점에서는 아직 getRecipesAW 함수가 미처 일을 끝내지 못한 상태이다.

    결국, console.log(rec)는 아직 resolved 되지 않은 pending 상태의 promise를 그대로 출력해버리고

    console.log(rec) 코드가 실행되고 난 한참 후에야 rec이라는 변수에 

    '432: Fresh tomato pasta'라는 원하던 데이터가 할당되게 되는 것.

     

    출력된 'Promise {<pending>}' 이라는 문장의 의미는,

    'rec은 promise이긴 하지만, 아직은 결과를 가지지 않아서 console에 출력할 수가 없네요 😋' 라는 내용이다.


    그러면, 이런 문제(함수가 promise를 return하고 나서, 해당 값을 출력하고싶다)를 어떻게 해결해야 할까?

    온고지신이라고 했던가. 허허 👴

    그 해결책은 바로 'then method'이다.

    async function getRecipesAW() {
      const IDs = await getIDs;
      console.log(IDs); // [523, 883, 432, 974]
      const recipe = await getRecipe(IDs[2]);
      console.log(recipe); // 432: Fresh tomato pasta
      const related = await getRelated('Jonas Schmedtmann');
      console.log(related); // Jonas Schmedtmann: Italian Pizza
    
      return recipe;
    }
    
    getRecipesAW().then(result => console.log(`${result} is the best ever!`));
    // 432: Fresh tomato pasta is the best ever!
    

    getRecipesAW 함수는 promise를 return하는데, 이 promise는 자동적으로 결과값을 가질 것이므로

    그 결과값이 온전히 return될 때까지 기다리기만 하면 된다.

    그래서, 그냥 then을 사용해주면 콜백 함수의 인자로 promise의 결과값을 받아올 수가 있게 된다.

     

    결국 promise가 결과값을 return하기 전까지 시간이 걸린다는 건 이전과 동일하므로

    역시나 이전과 동일하게 then 메소드를 사용해서 promise가 결과값을 가질 때까지 기다렸다가

    결과값이 나오고 나면 그때서야 그 'promise의 결과값'을 사용해주면 된다는 것.

    반응형

    댓글

Designed by Tistory.