cs

[JavaScript] 비동기 동작 관리를 위한 Promise와 async/await

ssoonn 2024. 12. 31. 12:28
세 줄 요약

1. Promise는 비동기 동작을 관리하기 위해 설계되었으며, 콜백 함수로 이루어져 있다.
2. Promise의 상태는 pending, fulfilled, rejected로 나뉜다. (상태에 따른 콜백이 finally, then, catch)
3. 비동기 함수의 처리를 쉽게 하기 위해 async/await 키워드를 사용한다.

 

overall

이 그림 이해하면 끝

  • 비동기 함수 (Producer = Executor)
    • 결과를 생성하는 함수로, Promise 내부에서 실행된다. 결과를 콜백의 Input에 넣는다.
    • caller
  • 성공 콜백, 실패 콜백 (Consumer = Executee)
    • Promise의 결과를 받아 처리하는 함수(then, catch).
    • callee
  • Promise의 역할
    • 비동기 작업의 결과(성공/실패)를 관리하며, Producer와 Consumer 간의 연결을 담당.

 

Promise 객체의 상태와 콜백

Promise 는 비동기를 위해 탄생한 개념(Callback + Asynchronous)이기에 비동기에 대해 상태를 갖는다.

  • 시작 Pending : 약속된 결과 값이 반환(다음 상태로 전이)되지 않은 상태, 초기 상태
  • 종료 Non-Pending
    • 성공 (이행) Fulfilled : 연산이 성공적으로 완료된 상태
    • 실패 (거부) Rejected : 연산이 실패한 상태

 

Pending 상태 후 → 성공 (이행) Fulfilled / 실패 (거부) Rejected 상태에 따라 다른 Callback 함수를 호출한다.

  • Promise 객체는 Executor 로 초기 인스턴스화되고 = Pending 상태의 객체
  • 성공 (이행) Fulfilled / 실패 (거부) Rejected 상태에 따라 최종 Promise 객체가 반환된다.
    • Promise.resolve 객체 = 성공 (이행) Fulfilled 상태의 객체
      • .then() 내 성공 Callback 함수인 resolve() 에서 처리된다.
    • Promise.reject 객체 = 실패 (거부) Rejected 상태의 객체
      • .catch() 내 실패 Callback 함수인 reject() 에서 처리된다.

 

*pending 상태: Promise 내부에서 작업이 진행중이므로, 콜백함수는 호출되지 않는다.

(진행 중인 작업 상태 추적 -> Promise의 executor 함수 내부에서 직접 로직을 추가)

cf. Promise 내 Caller 실행 시점은?

new Promise가 생성되는 즉시 Caller 함수가 실행된다.
Promise 객체를 생성하는 순간 내부의 executor 함수가 실행되며, 비동기 작업이 실행된다.

then이나 catch는 결과(resolve, rejected)를 처리하기 위해 등록된다.
promise의 상태는 resolved나 rejected로 "한 번만" 변경되며, 변경 이후에는 불변 상태이다. (Promise의 immutable한 특성)

 

Promise 예시

// Promise 생성
const myPromise = new Promise((resolve, reject) => {
  const success = true; // 성공 여부를 나타내는 변수
  
  if (success) {
    resolve("작업이 성공적으로 완료되었습니다!"); // 작업 성공 시 resolve
  } else {
    reject("작업에 실패했습니다!"); // 작업 실패 시 reject
  }
});

// Promise 결과 처리
myPromise
  .then((result) => {
    console.log(result);  // 성공 시 출력: "작업이 성공적으로 완료되었습니다!"
  })
  .catch((error) => {
    console.error(error); // 실패 시 출력: "작업에 실패했습니다!"
  });

 

  1. new Promise()를 사용해 새로운 Promise 객체 생성
  2. executor 함수 안에서 비동기 작업을 처리-> 작업이 성공하면 resolve(), 실패하면 reject() 호출
  3. .then()은 Promise가 성공적으로 완료되었을 때 호출, .catch()는 에러가 발생했을 때 호출

 

 

Promise Hell

다중으로 중첩된 Promise로 인해 코드가 복잡해지는 문제를 말한다.

여러 비동기 작업이 순차적으로 실행될 때 발생한다.

// 이렇게 짜지 마세요
step1(value1)
	.then((value2) => {
		step2(value2).
        	.then((value3) => {
            	step3(value3)
                	.then((value4) => {
                    	console.log(value4)
					})
			})
	})

 

해결법으로는 크게 두 가지가 있다.

  • Promise Chain
  • await/async

Promise Chain

Promise의 결과가 항상 Promise의 객체이기 때문에, .then을 계속해서 연결할 수 있다는 개념에서 비롯된다.

이를 통해 비동기 작업을 순차적으로 처리할 수 있다.

  • Promise 객체가 항상 then() 메서드를 반환하므로, 여러 then()을 연속으로 연결 가능
  • then()은 이전 Promise가 리턴한 값을 다음 then()으로 전달하여, 그 결과를 Promise로 반환
step1(value1)
	.then((value2) => {
    	return step2(value2)
	})
	.then((value3) => {
		return step3(value3)
	})
	.then((value4) => {
		console.log(value4)
	})

await / async

Promise는 기본적으로 Caller(Executor)과 Callee(Callback) 두 부분으로 구성된다.

async/await는 이 두 부분을 분리한 방식으로, Promise 객체를 더 직관적으로 다룰 수 있도록 해준다.

 

async 함수 안에서 await을 사용하여 비동기 작업을 마치 동기 처리처럼 작성할 수 있게 한다.

별도의 callback 함수 없이, await를 사용해 Promise의 결과를 기다리고, 그 결과를 외부 함수에서 직접 처리한다.

 

  • async: 함수 앞에 붙여서 이 함수가 비동기 함수임을 표기
    • Caller(Executor)를 정의하는 곳에서 사용.
    • 항상 Promise를 반환한다.
  • await: Promise가 완료될 때까지 기다리고, Promise가 성공적으로 완료되면 그 값을 반환
    • async 함수 내부에서만 사용될 수 있다.
    • 실패 처리는 try/catch를 통해 예외처리
      • 왜? awiat는 Promise가 reject되면 예외값을 던지기 때문에.

 

async function executeSteps() {
  try {
    const value2 = await step1(value1);  // step1 비동기 처리
    const value3 = await step2(value2);  // step2 비동기 처리
    const value4 = await step3(value3);  // step3 비동기 처리
    console.log(value4);  // 최종 결과 출력
  } catch (error) {
    console.error("에러 발생:", error);  // 에러 처리
  }
}

executeSteps();

 

  1. async function을 사용하여 비동기 함수인 executeSteps 선언
  2. 각 await은 해당 Promise가 해결될 때까지 기다림
    • e.g. await step1(value1)은 step1이 완료될 때까지 기다리고, 그 결과를 value2에 할당
  3. try/catch 블록을 사용한 에러처리
    • 어떤 단계에서 에러 발생 시 catch에서 처리

 

 

Promise Chain vs. await/async

  Promise Chain Async/Await
사용방식 then() 체인을 통해 여러 비동기 작업을 연결 async 함수 내에서 await을 사용해 비동기 처리
가독성 선형적 코드 -> 가독성 :) 동기 방식처럼 코드를 작성 -> 매우 직관적
에러 처리 catch()를 사용하여 전체 체인에서 에러 처리 가능 try/catch 블록으로 동기식처럼 에러 처리 가능
비동기 처리 then()을 연결하여 순차적으로 처리 await을 사용하여 동기식처럼 비동기 처리
(= 순차적으로 비동기 작업 처리)
코드 복잡성 코드 깔끔, 복잡성 줄어듦 가장 간결하고 직관적인 방식

 

 

ref.

https://www.freecodecamp.org/news/learn-promise-async-await-in-20-minutes/

 

How to Learn JavaScript Promises and Async/Await in 20 Minutes

By Thu Nghiem On the web, many things tend to be time-consuming – if you query an API, it can take a while to receive a response. Therefore, asynchronous programming is an essential skill for developers. When working with asynchronous operations in J...

www.freecodecamp.org

https://betterprogramming.pub/javascript-fundamentals-async-await-99bf7ad667d1

 

JavaScript Fundamentals: Async/Await

Make your code cleaner, more readable with the best feature in ES8

betterprogramming.pub

https://medium.com/dailyjs/asynchronous-adventures-in-javascript-promises-1e0da27a3b4

↑정말 정리 잘해놓은 아티클이니 이해가 안되신다면 원문을 읽어보세요

 

Asynchronous Adventures in JavaScript: Promises

Why Promises?

medium.com