[JavaScript] Promise와 async-await

지혜·2023년 9월 23일
0
post-thumbnail

🌟 비동기

독립적으로 돌아가는 작업을 비동기 작업이라고 한다.

즉, 동시에 여러 가지 작업을 할 수 있는 개념

비동기로 처리되는 작업

비동기를 동기로 동작시키는법

  • Callback Function
    • 함수가 많아질 수록 함수의 깊이가 깊어지기 때문에 callback 지옥이 되는 것 -> 가독성 저하
    • 각각의 fetcher 에서 다음 작업을 호출하게 되는데, 이렇게 하면 우리가 순차 보장이 가능하다.
  • Promise
    • 비동기를 동기식으로 동작하게 만드는 대표적인 객체
    • 동작 성공시 실행될 resolve라는 함수와 실패시 동작할 reject 2개의 인자를 받는다.
    • 또한 Promise 객체들은 then 을 통해 이어질 수 있고 catch 를 통해 에러 캐치를 할 수 있다.
  • Async-Await
    • 말 그대로 동작을 일시정지 시킨 것처럼 보이게 해준다.
    • Async Function을 만들고 내부에서 await를 이용하여 스케줄 순서를 정하게 된다.
    • 가독성이 좋고 인터페이스에 묶이지 않기 때문에 코드 분리가 쉽고 모듈화하기 좋다.

🌠 Promise

비동기 작업의 단위를 말한다.

기본 사용법

  • Promise에서 요구하는 방법대로 만들어야 한다. → new Promise(...) 생성
    const promise1 = new Promise((resolve, reject) => {
    	// 비동기 작업
    })
    • const로 선언했기 때문에 재할당되지 않는다.
      하나의 변수로 끝까지 해당 Promise를 관리하는 것이 가독성도 좋고 유지보수하기 좋다.
    • new Promise(…) 로 Promise객체 생성
    • 생성자는 화살표 함수(executor)를 인자로 받는다. resolve 는 executor 내에서 호출할 수 있는 또 다른 함수 = 성공 함수 reject executor 내에서 호출할 수 있는 또 다른 함수 = 실패 함수
    • new Promise()는 기다리지 않고 바로 호출해버린다.
  • Promise가 끝나고 난 다음의 동작을 then 메소드와 catch 로 설정한다.
    • then 메소드는 해당 Promise가 성공했을 때 동작을 지정한다. 인자로 함수를 받는다.

    • catch 메소드는 해당 Promise가 실패했을 때의 동작을 지정한다. 인자로 함수를 받는다.

      const promise1 = new Promise((resolve, reject) => {
        resolve(); // 성공시 호출하는 함수 -> then을 호출한다.
      });
      
      promise1
        .then(() => {
          console.log("then!"); // 성공시 호출
        })
        .catch(() => {
          console.log("catch!"); // 실패시 호출
        });

재사용하기

  • 매번 비동기 작업을 할 때마다 new Promise(…) 할 수 없기 때문에 그대로 리턴하는 함수를 만든다.
function startAsync(age) { // 재사용하기 위한 함수 생성
  return new Promise((resolve, reject) => { 
    if (age > 20) resolve();
    else reject();
  });
}

const promise1 = startAsync(25);
promise1
  .then(() => {
  console.log("1 then!");
})
  .catch(() => {
  console.log("1 catch!");
});

const promise2 = startAsync(15);
promise2
  .then(() => {
  console.log("2 then!");
})
  .catch(() => {
  console.log("2 catch!");
});

작업 결과 전달하기

  • resolvereject 함수에 인자를 전달함으로써 then 및 catch 함수에서 비동기 작업으로부터 정보를 얻을 수 있다.
function startAsync(age) {
  return new Promise((resolve, reject) => {
    if (age > 20) resolve(`${age} success`);
    else reject(new Error(`${age} is not over 20`));
  });
}

const promise1 = startAsync(25);
promise1
  .then((value) => {
  console.log(value);
})
  .catch((error) => {
  console.error(error);
});

const promise2 = startAsync(15);
promise2
  .then((value) => {
  console.log(value);
})
  .catch((error) => {
  console.error(error);
});
  • Promise는 비동기 작업을 생성/시작하는 부분(new Promise())과 작업 이후의 동작 지정 부분(then, catch)을 분리함으로써 보다 유연한 설계를 가능토록 한다.
    하지만 Promise를 생성한 후에 이 상태가 실제로 어떤지 확인할 수 없다.
    function setTimeoutPromise(delay) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve();
        }, delay);
    		// setTimeout(resolve, delay); 요약해서 한줄로 작성 가능
      });
    }
    
    console.log("setTimeoutPromise가 시작됩니다.");
    
    const promise = setTimeoutPromise(1000);
    
    promise.then(() => {
      console.log("끝났습니다!");
    });

요약하면 아래와 같이 코드를 줄일 수 있다.

    function setTimeoutPromise(delay) {
      return new Promise((resolve) => setTimeout(resolve, delay));
    }
    
    console.log("setTimeoutPromise가 시작됩니다.");
    
    const promise = setTimeoutPromise(1000);
    
    promise.then(() => {
      console.log("끝났습니다!");
    });

🌠 async-await

함수를 선언할 때 붙여주는 키워드

async : 비동기 작업을 만드는 방법

  • new Promise()를 리턴하는 함수를 async 함수로 변환하기

    // 기존
    // function startAsync(age) {
    //   return new Promise((resolve, reject) => {
    //     if (age > 20) resolve(`${age} success`);
    //     else reject(new Error(`${age} is not over 20`));
    //   });
    // }
    
    async function startAsync(age) {
      if (age > 20) return `${age} success`;
      else throw new Error(`${age} is not over 20`);
    }
    
    const promise1 = startAsync(25);
    promise1
      .then((value) => {
        console.log(value);
      })
      .catch((error) => {
        console.error(error);
      });
    
    const promise2 = startAsync(15);
    promise2
      .then((value) => {
        console.log(value);
      })
      .catch((error) => {
        console.error(error);
      });
    • 함수에 async 키워드를 붙인다.
    • new Promise() 부분을 없애고 executor 본문 내용만 남긴다.
    • resolve(value) 부분을 return value; 로 변경한다.
    • reject(new Error()); 부분을 throw new Error()로 수정한다.
  • async 함수의 리턴 값은 무조건 Promise 이다.

    • Promise 함수에서는 문자열을 리턴했는데 이 코드에서는 흐름을 제어해야 하기 때문에 promise1과 promise2는 문자열이 아닌 걸 볼 수 있다.

await : 작업 끝날 때까지 기다리기

  • Promise가 fulfilled되든지 rejected가 되든지 끝날 때까지 기다리는 함수
  • async 함수 내부에서만 사용할 수 있다.
    function setTimeoutPromise(delay) {
      return new Promise((resolve) => setTimeout(resolve, delay));
    }
    
    async function startAsync(age) {
      if (age > 20) return `${age} success`;
      else throw new Error(`${age} is not over 20`);
    }
    
    async function startAsyncJobs() {
      await setTimeoutPromise(1000);
      const promise1 = startAsync(25);
    
      try {
        const value = await promise1;
        console.log(value);
      } catch (e) {
        console.error(e);
      }
    
      const promise2 = startAsync(15);
    
      try {
        const value = await promise2;
        console.log(value);
      } catch (e) {
        console.error(e);
      }
    }
    
    startAsyncJobs();
    • 문법적으로 await [[Promise 객체]] 이렇게 사용한다.
    • await 은 Promise 가 완료될 때까지 기다린다. setTimeoutPromise 의 executor 에서 resolve 함수가 호출될 때까지 기다린다. 그 시간동안 startAsyncJobs 의 진행은 멈춰있다.
    • await 은 Promise 가 resolve 한 값을 내놓는다. async 함수 내부에서는 리턴하는 값을 resolve 한 값으로 간주하므로, ${age} success 가 value로 들어온다는 점을 알 수 있다.
    • 해당 Promise 에서 reject 가 발생한다면 예외가 발생한다. 이 예외 처리를 하기 위해 try-catch 구문을 사용했다. reject 로 넘긴 에러(async 함수 내에서는 throw 한 에러)는 catch 절로 넘어갑니다. 이로써 익숙한 에러 처리 흐름으로 진행할 수 있다.
  • 비동기 환경에서 비동기 작업의 결과를 기다리는 것은 다소 의미가 있다. 예를 들어 생성들을 모두 손질해야 요리를 시작할 수 있는 것과 같을 때, await 사용하면 된다.
  • async 와 await을 쓰면 실제 작업이 끝나는 걸 기다린 다음 다음 코드를 수행한다는 느낌이다.

await를 사용할 수 없는 상황일 때,

  • 특정 라이브러리에서 프로그래머에게 요구하는 동작의 형태가 일반 함수여야 하는 케이스에 그렇다.
  • 예를 들어, setTimeoutasync 함수 안에서 await 을 걸 수가 없다.
    setTimeout 은 무조건 콜백 함수를 인자로 받기 때문에!!!
    그래서 setTimeout을 감싸는 Promise 를 만들었다.

📃 정리

  • 기다리기만 하면 되는 작업을 비동기로 처리할 수 있습니다. 동시에 여러 작업이 진행되어서 비교적 효율적이지만, 흐름 제어는 동기 코드보다 어렵다.
  • Promise 를 생성할 때에는 resolvereject 함수를 적절히 호출하는 작업을 넣어주고, 이후 생성된 Promise 에 대해 thencatch 메서드를 호출하여 후속 조치를 정해준다.
  • new Promise(…)는 async 함수로 적절하게 변환할 수 있다.
  • async 함수 내에서 Promise 에 대해 await 을 걸어서 작업을 기다릴 수 있다.
  • 스타일은 되도록 일관되게 작성하는 게 좋다. async-await 을 사용하든지,
    resolve-reject-then-catch 를 사용한다.
  • 여러 Promise 를 동시에 기다리려면 Promise.all 를 사용한다.

📁 참고

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/async_function
https://springfall.cc/article/2022-11/easy-promise-async-await

profile
Junior Frontend Developer

0개의 댓글