ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 비동기 - #3. Async & Await
    Front-End(Web)/Javascript 2021. 2. 14. 19:09
    반응형

    프로젝트를 마무리하고, 이제서야 비동기 3탄이자 꽃인 async/await 에 대해 포스팅을 한다.

    async/await 은 JS의 비동기 처리를 개선하기 위해(콜백지옥), Promise 처럼 ES6 이후 문법에서 등장했으며 가장 최신의 문법이다.

     

    Promise를 다루는 .then() 에 비해 어떠한 점이 더 나아진건지 한 번 확인해보도록 하겠다! 


    📒 Async & Await

    해당 함수를 비동기로 실행하기 위해 사용되는 함수가 바로 이 'Async Function' 이다. 당연히, 결과값은 Promise를 반환한다.

    Async Function의 장점 중 하나는, 코드의 구조가 일반 동기함수와 매우 유사하게 사용할 수 있다는 것이다.

    // Promise
    
    const getItem = () => {
      fetch("API주소")
        .then(res => res.json())
        .then(result => console.log(result))
    }
    
    
    // Async/Await
    
    const getItem = async () => {
      const response = await fetch("API주소")
      const result = await response.json()
      console.log(result)
    }

    같은 fetch 로직("GET")을 Promise(then) 과 Async/Await 으로 각각 쓴 것이다.

     

    1. Async 문법

    // Promise
    function foo() {
        return Promise.resolve(1)
    }
    
    // Async Function
    async function foo() {
        return 1
    }

    async는 위처럼 function 앞에 위치한다. 이 함수는, 결과값으로 Promise를 반환하게 된다. (Promise가 아닌 것은 Promise로 감싼다.)

    또, 이 Async Function 만의 특징은 바로 비동기로 처리하는 'await' 을 함수 내에서 사용할 수 있다는 점이다!

     

    2. Await 문법

    const value = await fetchItem;

    await은 Async Function 내에서만 사용할 수 있는 문법이다. (바깥에서 사용하면 SyntaxError 발생!)

     

    async 함수가 호출되면, 함수 내 구문들이 실행되다가 await을 만나면 해당 줄에서 잠시 실행이 중단된다.

    이후, 처리가 완료되어 Promise 값이 들어온다면, await은 비동기 처리가 완료되었음을 인식하고 해당 지점부터 재실행을 이어간다.

     

    async function awaitTest() {
    
      let promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve("완료!"), 3000)
      });
    
      let result = await promise; // Promise가 이행될 동안 기다림(3초)
    
      alert(result); // "완료!"
    }
    
    awaitTest();

    해당 예제를 참고해보자. awaitTest() 라는 함수는 Async Function 이고, 그 안에는 promise() 라는 비동기 함수가 있다.

    이는, 3초 후에 "완료!" 문자열을 Promise 형태로 반환을 하는 비동기 함수이다. 이를 await을 통해 실행시키면 어떻게 될까?

     

    함수가 실행되다가 await 줄에서 Promise를 기다릴 것이고, result 변수엔 3초 뒤에 resolve 된 "완료!" 문자열이 저장될 것이다.

    그런 직후, 함수는 재실행되면서 alert() 를 처리하고, 여기엔 result 값인 "완료!"가 띄어질 것이다.

     

     

    - Await 의 장점

    1. Await은 Promise가 처리되길 기다리는 동안 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 진행할 수 있다. 그렇기에, 로딩이 효율적이어지고 CPU 리소스가 낭비되지 않는다는 장점이 있다.
    2. Await은 promise.then보다 좀 더 세련되게 프라미스의 result 값을 얻을 수 있도록 해주는 문법입니다. promise.then보다 가독성 좋고 쓰기도 쉽습니다. (일반 함수구문과 유사)

     

    - Await 사용 시 주의사항

     

    1) 일반 함수에서는 사용이 불가하다!

    function f() {
      let promise = Promise.resolve(1);
      let result = await promise; // Syntax error
    }

    이처럼, Async Function 외에서 사용하면 문법 에러가 발생한다. 

     

    2) 최상위 레벨에서 사용이 불가하다!

    // 에러 발생
    let response = await fetch('/article/promise-chaining/user.json');
    let user = await response.json();
    
    // async로 감싸줘야 한다!
    (async () => {
      let response = await fetch('/article/promise-chaining/user.json');
      let user = await response.json();
      ...
    })();

    1번과 유사한 얘기겠지만, await은 당연히 최상위 레벨에서 단독적으로 사용이 불가하다.

    Async Function 으로 감싸주면서 이를 사용할 수 있으며, 별도의 전역변수에 결과값을 저장하는 등의 방법을 활용할 수 있겠다.

     

     

    3. 에러 핸들링

    Async/Await 의 에러 핸들링 문법인 try/catch 에 대해 알아보도록 하자.

    async function f() {
      throw new Error("에러 발생!");
    }
      .catch(err => console.log(err))

    이 방법은, Promise 의 catch() 메서드를 통해 에러를 잡아내는 문법이다. 

    그런데, 이 문법은 에러를 확인하고 throw 하기까지 지연현상이 발생할 수 있다. 이를 좀 더 최적화한 문법이 바로 try/catch 인 것이다.

     

    async function foo() {
      try {
        let response = await fetch('http://유효하지-않은-url');
        let user = await response.json();
      } 
      catch(err) {
        // fetch와 response.json에서 발행한 에러 모두를 여기서 잡음
        alert(err);
      }
    }
    
    foo();

    이처럼, try에는 우리가 비동기 함수에서 실행할 구문을, catch(err) 에서는 에러 핸들링할 방법을 작성하면 된다.

    try/catch 의 장점은, try에서 발생하는 모든 에러를 catch()에서 잡아내며, 에러가 발생하면 함수 스코프가 catch()로 넘어가게 된다.


    👊 Promise -> Async/Await✨ (드림코딩 엘리)

    지난 포스팅에서, Callback 지옥을 Promise 문법으로 개선했다. 이를, Async/Await 문법으로 재차 개선하여 코드 가독성을 높여보자!

     

    - Promise

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

     

    - Async/Await

    class UserStorage {
      loginUser(id, pw) {             
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (
              (id === 'ellie' && pw === 'dream') ||
              (id === 'coder' && pw === 'academy') 
            ) {
                resolve(id);
              } 
            else {
              reject(new Error('not found'));
            }
          }), 2000);
        })
      }
    
      getRoles(user) {
    	return new Promise((resolve, reject) => {
          setTimeout(() => {
            if (user === 'ellie') {
              resolve({name: 'ellie', role: 'admin'});
            } 
            else {
              reject(new Error('no access'));
            }
          }, 1000);
        })
      }
      
      // Async/Await
      async getUserWithRole = (user, password) => {
        const user = await this.loginUser(user, password);
        const role = await this.getRoles(user);
        return role;
      }
    }
    
    const userStorage = new UserStorage();
    const id = prompt('enter your id');
    const pw = prompt('enter your pw')
    // Async/Await
    userStorage.geetUserWithRole()
      .then(console.log);
      .catch(console.log);

    Async/Await 이 Promise 에 비해 엄청난 이점이 있다고 생각했지만, 막상 그런건 또 아니었다.

    await 에서 스코프가 대기하면서 함수처리가 좀 더 효율적인 점, 그리고 일반함수 형태와 유사하여 가독성이 높아진다는 점 정도인 것이다.

     

    또한, 공식문서와 각종 사이트를 보아도 async/await 을 반드시 써야 하는 것은 아니고, 경우에 따라 Promise 메서드를 사용하기도 한다고.

    (아마, race()나 all() 과 같이 다른 형태의 비동기를 처리할 때가 포함될 것 같다?!)

     

    앞으로, 프로젝트나 향후 현업에서 근무하면서 항상 비동기 처리에 있어 이 둘의 장단점에 대해 짚고 넘어가도록 해야겠다.

     

     

    [출처]

    - MDN Web DOCS: developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function  

    - Javscript.INFO: ko.javascript.info/async-await  

    - 박성룡 님의 블로그: pks2974.medium.com/javascript-%EC%99%80-async-await-111fdad3c20d  

    - Youtube 드림코딩 엘리(13. 비동기의 꽃 async/await) : www.youtube.com/watch?v=aoQSOZfz3vQ

     

    [비동기 포스팅 시리즈]

    - #1. 비동기 개념과 Callback Function : abangpa1ace.tistory.com/74

     

    [Javascript] 비동기 - #1. 비동기 개념과 Callback Function

    Javascript 메서드를 사용하면 인자로 받는 함수를 '콜백함수' 라고 명명하는 것을 보았을 것이다. React 작업을 하면서, fetch() 함수의 .then 혹은 async/await 등 '비동기 프로그래밍'을 자주 적용했다. 위

    abangpa1ace.tistory.com

    - #2. Promise : abangpa1ace.tistory.com/76

     

    [Javascript] 비동기 - #2. Promise

    나도 그렇고, fetch()-then 이나 async/await 을 사용하면 최초에 Promise 라는 결과값이 반환되는 것을 볼 수 있었을 것이다. 또한, 자세히 보면 이 Promise는 객체(Object) 형태를 하고 있으며, 아래와 같이

    abangpa1ace.tistory.com

     

    반응형
Designed by Tistory.