Promise, async/await 복습

2022. 7. 8. 13:21

  • Promise는 비동기 작업의 완료 결과를 알려주는 객체이다.
  • new Promise에 전달되는 함수(실행자 함수, executor)는 자동으로, 동기적으로 실행되며
    resolve, reject 라는 2개의 콜백함수를 받는다.
  • executor 함수의 작업이 끝나는 대로 결과에 따라서 resolve / reject 중 하나의 함수를 호출한다.
  • 정확히는 작업이 성공했을 경우, 결과를 나타내는 value와 함께 resolve(value) 호출
  • resolvePromise가 성공적으로 완료되었을 때 결과를 반환하기 위해 설계된 내부 콜백 함수
  • resolve() 호출 = Promise 완료(성공)
  • 에러 발생했을 경우 에러 객체를 나타내는 error와 함께 reject(error) 호출
  • then은 비동기작업이 끝나고 실행될 함수이며, 실행결과에 따른 값을 받아서 실행된다.
  • new Promise가 반환한 프로미스 객체는 state, result라는 내부 프로퍼티를 갖는다.
  • state: pending -> fulfilled / reject
  • result: undefined -> value / error
  • 프로미스 작업은 MicroTask Queue에 들어가서 처리되기 때문에 프로미스 핸들링은 항상 비동기로 처리된다.
  • cf. macrotask queue는 프로미스와 직접적 연관은 없지만,
    자바스크립트 엔진은 매크로태스크 하나를 처리할 때마다 다른 매크로태스크나 렌더링을 작업하기 전에 마이크로태스크큐에 쌓인 태스크를 전부 처리한다.
    • Event Loop의 알고리즘:
      • macrotask queue에서 가장 오래된 태스크를 꺼내 실행
      • 모든 microtask를 실행(큐가 빌 때까지)
      • 렌더링할 것이 있으면 처리(Animation Frames)
      • macrotask queue가 비어있으면 새로운 macrotask가 나타날 때까지 기다림
      • 이 순서를 반복
      • 즉, Microtask Queue > Animation Frames > Macrotask Queue 의 우선순위로 콜스택에 이동된다.
      • Promise -> Microtask Queue
      • RequestAnimationFrame -> Animation Frame
      • Timeout -> Task Queue

 

 

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');
  // setTimeout(() => resolve('결과'), 2000);
  setTimeout(() => reject('결과'), 2000);
});

promise //
  .then(
    (result) => console.log('resolve', result),
    (result) => console.log('reject', result),
  );

executor 
(2초 뒤)
reject 결과

위는 이해를 위한 간단한 예시이며 reject에서는 보통 에러를 다음과 같이 처리한다.

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');
  // setTimeout(() => resolve('결과'), 2000);
  setTimeout(() => reject(new Error('에러')), 2000);
});

promise //
  .then(
    (result) => console.log('resolve', result),
    (result) => console.log('reject', result),
  );

executor
(2초 뒤)
reject Error: 에러

 

비동기 작업 후처리 할 때
then에 첫 번째 인자는 성공했을 경우 처리할 함수, 두 번째 인자에는 실패했을 경우 처리할 함수를 처리할 수 있는데
이 방법보다는 catch를 사용하여 에러 처리를 하는 것이 더 직관적

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');
  // setTimeout(() => resolve('결과'), 2000);
  setTimeout(() => reject(new Error('에러')), 2000);
});

promise //
  .then((result) => console.log('resolve', result))
  .catch((error) => console.log('reject', error));

 

지금까지 위 예시에서는 resolve를 주석처리했는데, 이 부분을 해제한다면

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');

  setTimeout(() => resolve('결과'), 2000);
  setTimeout(() => reject(new Error('에러')), 2000);
});

promise //
  .then((result) => console.log('resolve', result))
  .catch((error) => console.log('reject', error));

executor
(2초 후)
resolve 결과

의 결과를 반환한다. 그 이유는 resolve 함수가 더 위에 있기 때문.
만약 reject가 먼저 실행되는 코드라면 에러를 반환한다.
아무튼 결론은 promise의 executor는 하나의 콜백함수만 호출한다는 것이다.

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');
  resolve(123);
});

promise //
  .then((result) => console.log('resolve', result))
  .catch((error) => console.log('reject', error));

executor
resolve 123

 

추가로, 성공 여부에 상관없이 작업이 종료되면 실행될 finally
성공여부를 몰라도 되기 때문에 전달하는 인수가 없다.

const promise = new Promise((resolve, reject) => {
  // executor ...
  console.log('executor');
  // setTimeout(() => resolve('결과'), 2000);
  setTimeout(() => reject(new Error('에러')), 2000);
});

promise //
  .finally(() => console.log('필수'))
  .then((result) => console.log('resolve', result))
  .catch((error) => console.log('reject', error));

executor
(2초 후)
필수
reject Error: 에러

 


프로미스 체이닝

다음 코드는 2초 뒤 1 2 4를 호출한다.

new Promise(function (resolve, reject) {
  setTimeout(() => resolve(1), 1000); // (*)
})
  .then(function (result) {
    console.log(result); // 1
    return result * 2;
  })
  .then(function (result) {
    console.log(result); // 2
    return result * 2;
  })
  .then(function (result) {
    console.log(result); // 4
    return result * 2;
  });
  • result가 핸들러 체인을 통해 다음 .then 핸들러로 전달됨
  • 프로미스 체이닝이 가능한 이유는 promise.then을 호출하면 프로미스가 반환되기 때문
  • 핸들러가 값을 반환할 때 이 값이 프로미스의 result가 됨
  • 핸들러가 프로미스를 생성하거나 반환할 수도 있다.
new Promise(function (resolve, reject) {
  setTimeout(() => resolve(1), 2000);
})
  .then(function (result) {
    console.log(result); // 1

    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(result * 2), 3000);
    });
  })
  .then(function (result) {
    console.log(result); // 2

    return new Promise((resolve, reject) => {
      setTimeout(() => resolve(result * 2), 4000);
    });
  })
  .then(function (result) {
    console.log(result); // 4
  });

(2초 후) 1
(3초 후) 2
(4초 후) 4

 

async await

  • async 키워드를 붙이면 해당 함수는 프로미스를 반환한다. 프로미스가 아닌 것은 프로미스로 감싸 반환
  • await 키워드는 프로미스가 처리될 때까지 기다리고 결과를 반환한다.
  • 이는 Promise보다 가독성 좋고 쓰기도 쉽다.

 

 

참조: 

https://ko.javascript.info/promise-basics

 

프라미스

 

ko.javascript.info

 

https://joshua1988.github.io/web-development/javascript/promise-for-beginners/

 

자바스크립트 Promise 쉽게 이해하기

(중급) 자바스크립트 입문자를 위한 Promise 설명. 쉽게 알아보는 자바스크립트 Promise 개념, 사용법, 예제 코드. 예제로 알아보는 then(), catch() 활용법

joshua1988.github.io


+

모던 자바스크립트 Deep Dive를 보고 공부한 내용을 추가로 노션에 정리:

https://mnmhbbb.notion.site/Promise-fetch-async-await-w-axios-Modern-JS-Deep-Dive-1d474c1bf16080279293f22d1bfb7732?source=copy_link

 

Promise, fetch, async/await 복습(w. axios, Modern JS Deep Dive) | Notion

프로미스의 기본

mnmhbbb.notion.site