일기

Promise

Realuda72 2025. 2. 11. 22:40

Promise - JavaScript | MDN

 

Promise - JavaScript | MDN

Promise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.

developer.mozilla.org

 Promise에 대해서는 위의 mdn 페이지에서 설명하고 있지만 사실 잘 못알아먹겠다.

 간단하게나마 내가 이해한 Promise를 정리해보려 한다.

 

 우선 Promise는 javascript에서 비동기 처리를 하기 위한 객체이다. Promise 객체는 지금 당장 값을 정할 수 없지만, 미래에 반드시 값을 정해줄 것을 약속한다.

const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("서버에서 받은 데이터");
  }, 2000);
});

console.log(fetchData); 
// 즉시 출력하면 Promise {<pending>} (아직 값이 없음)
// 2초 후에 "서버에서 받은 데이터"가 들어옴

 위의 예시에서 fetchData는 Promise 객체이며, 지금 당장은 값을 가지고 있지 않지만 미래에 값을 가져올 것을 약속한다. 실제로 fetchData를 할당하고 즉시 로그를 찍어보면 아직 값이 없지만, 2초 후에 로그를 찍는다면 서버에서 받은 데이터가 할당되어있을 것이다.

 Promise의 생성자

new Promise(executor);

 Promise의 생성자는 위와 같이 정의되어있다. executor는 두 콜백 함수 resolve와 reject를 인수로 갖는다. 위의 예시에서 확인할 수 있다.

// executor를 화살표 함수로 표현
new Promise((resolve, reject) => {
	// do something
});

 위의 예시에서처럼 executor를 화살표 함수로 표현하는 방식을 많이 사용한다.

 executor의 내부에서 언제라도 resolve와 reject를 호출할 수 있다.

Promise의 상태

 Promise객체는 3개의 상태 중 하나를 가진다.

  • 대기(Pending): 아직 이행되지도, 거부되지도 않은 상태
  • 이행(Fulfilled): executor가 성공적으로 실행된 상태
  • 거부(Rejected): executor가 실패한 상태

 Promise의 성공과 실패 여부는 executor 내부에서 결정된다. executor 연산을 수행하다가 resolve를 실행하게 되면 성공, reject를 실행하게 되면 실패로 상태가 바뀐다.

Promise의 처리기

 처리기(Handler)는 Promise객체의 상태가 바뀔 때 실행되는 함수이다. 즉, Promise 내부의 resolve() 또는 reject() 함수가 호출되었을 때 실행되는 콜백 함수이다.

 then()은 Promise의 상태가 성공(fulfilled)가 되면 실행된다.

 catch()는 Promise의 상태가 실패(rejected)가 되면 실행된다.

 finally()는 Promise의 상태와 상관없이 실행된다.

new Promise((resolve, reject) => {
	const success = doSomething();
	if(success){
		resolve("성공");
	}else{
		reject("실패");
	}
})
	.then(result => console.log(result))
	.catch(error => console.error(error));
	.finally(() => console.log("끝"));

 위의 예시에서 Promise 객체는 어떤 비동기 함수 doSomething()을 실행한다. 그리고 그 결과 success에 따라서 resolve또는 reject함수를 실행한다.

 만약 executor 내부에서 resolve 함수를 실행하게 된다면 Promise 객체의 상태는 fulfilled로 바뀌게 되고, 자동적으로 then함수가 실행된다.

 만약 executor 내부에서 reject 함수를 실행하게 된다면 Promise 객체의 상태는 rejected로 바뀌게 되고, 자동적으로 catch함수가 실행된다.

 그리고 마지막으로 Promise 객체의 상태와 상관없이 finally 함수가 실행된다.

Promise의 체이닝

 위의 처리기를 통해 여러 비동기 작업을 동기적으로 처리할 수 있다. 과거에는 Promise를 사용하지 않고 콜백 함수를 통해 비동기 작업을 처리했다.

function fetchData(callback) {
  console.log("작업 시작");
  setTimeout(() => {
    console.log("작업 완료");
    callback("서버 데이터");
  }, 2000);
}

fetchData((result) => {
  console.log("받은 데이터:", result);
});
// 작업 시작
// (...2초 후)
// 작업 완료
// 받은 데이터: 서버 데이터

 위의 예시에서 fetchData는 2초 후에 데이터를 가져오고, callback함수를 호출함으로써 다음 작업을 처리한다. 이 방식으로도 비동기 작업을 잘 처리할 수 있지만, 콜백 함수를 호출하는 과정에서 인덴트의 깊이가 한 층 깊어지게 되는 문제가 발생한다. 만약 여러 작업을 순서대로 비동기적으로 처리하고 싶다면, 콜백 함수 안에 콜백 함수 안에 콜백 함수 안에... 콜백 함수를 넣어야할 것이다. 결과적으로 코드의 가독성이 떨어지고, 유지보수가 어려워진다.

function step1(callback) {
  setTimeout(() => {
    console.log("Step 1 완료");
    callback();
  }, 1000);
}

function step2(callback) {
  setTimeout(() => {
    console.log("Step 2 완료");
    callback();
  }, 1000);
}

function step3(callback) {
  setTimeout(() => {
    console.log("Step 3 완료");
    callback();
  }, 1000);
}

function step4(callback) {
  setTimeout(() => {
    console.log("Step 4 완료");
    callback();
  }, 1000);
}

// 콜백 피라미드 발생
step1(() => {
  step2(() => {
    step3(() => {
      step4(() => {
        console.log("모든 작업 완료!");
      });
    });
  });
});

 

 반면 Promise객체를 사용하면 코드 가독성이 개선된다.

// then() 체이닝
step1()
  .then(step2)
  .then(step3)
  .then(step4)
  .then(() => {
    console.log("모든 작업 완료!");
  });

 

 여러 then 처리기를 연결했더라도 중간에 하나라도 실패하면 즉시 가장 가까운 catch()로 이동하게 된다.

그 외

 Promise객체를 더 잘 사용하기 위한 기법을 몇가지 알아보자.

Promise.all()

Promise.all() 메서드는 순회 가능한 객체(배열 또는 유사배열객체)로 주어진 모든 Promise가 이행된 후, 혹은 Promise가 주어지지 않았을 때 이행하는 Promise를 반환한다. 주어진 Promise중 하나라도 거부된 경우, 첫 번째로 거부된 Promise의 error를 사용해 자기자신도 거부한다.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// Expected output: Array [3, 42, "foo"]

 

 순회 가능한 객체의 각 Promise의 결과값을 배열에 담아 반환한다.

Promise.allSettled()

 Promise.allSettled() 메서드는 주어진 모든 Promise를 이행하거나 거부한 후, 각 Promise의 결과를 나타내는 객체 배열을 반환한다.

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) =>
  setTimeout(reject, 100, 'foo'),
);
const promises = [promise1, promise2];

Promise.allSettled(promises).then((results) =>
  results.forEach((result) => console.log(result.status)),
);

// Expected output:
// "fulfilled"
// "rejected"

 각 Promise의 성공 또는 실패 여부와 상관없이 결과값을 알고 싶을 때 사용한다.

Promise.race()

 Promise.race() 메서드는 순회 가능한 객체로 주어진 여러 Promise 중 가장 먼저 완료된 것의 결과값을 그대로 이행하거나 거부한다.

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then((value) => {
  console.log(value);
  // Both resolve, but promise2 is faster
});
// Expected output: "two"

Promise.any()

 Promise.any() 메서드는 순회 가능한 객체로 주어진 여러 Promise 중 하나라도 이행된다면 이를 이행값으로 하는 Promise 객체이다. 만약 주어진 모든 Promise가 거부되면 이 메서드도 거부되며, 각 Prmoise가 거부된 사유가 배열로 반환된다.

const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

const promises = [promise1, promise2, promise3];

Promise.any(promises).then((value) => console.log(value));

// Expected output: "quick"

'일기' 카테고리의 다른 글

로아M 비전프리뷰 후기  (2) 2025.06.20
자동 전투 게임 - 로비 UI 만들기  (0) 2025.03.17
인벤토리  (0) 2025.02.06
TCP 소켓 프로그래밍 실습  (0) 2025.01.17
Protocol Buffers  (0) 2025.01.07