인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

Droid님의 프로필 이미지

작성한 질문수

진짜! 자바스크립트(Javascript) - 기초부터 고급까지

Task Queue의 우선순위 📕

then 메서드에서 Promise 를 반환하는 코드에 대한 질문입니다.

해결된 질문

작성

·

84

0

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

promise
  .then((value) => {
    console.log(`${value} 번째 Promise 의 then`);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(2);
      }, 1000);
    });
  })
  .then((value) => {
    console.log(`${value} 번째 Promise 의 then`);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('강제 에러'));
      }, 1000);
    });
  })
  .catch((error) => {
    console.log(error);
  });

이런 형태로 then 과 catch 를 메서드 체이닝할 수 있는 이유는 then 과 catch 메서드가 Promise 를 반환하기 때문이라고 이해를 했습니다. 그런데 then() 메서드에 전달하는 콜백 함수에서 return new Promise() 를 하여 Promise 를 반환하면 해당 Promise 가 반환된다고 이해를 했는데 어떻게 그것이 가능한 지 잘 이해가 안됩니다. then 은 동기적으로 실행되고 then 메서드에 전달된 콜백은 마이크로 태스크 큐에 등록된 후에 콜스택이 모두 비어졌을 때 호출된다고 이해를 했는데... 연결고리가 이어지지 않는 느낌입니다.

답변 2

0

코딩맥스 CodingMax님의 프로필 이미지
코딩맥스 CodingMax
지식공유자

안녕하세요. Droid 님 질문 주셔서 감사합니다! 😊

then() 메서드의 반환값을 좀 더 자세히 살펴보겠습니다.

 

new Promise((resolve, reject) => {
	resolve(10);
})

 

하면 결과로 fulfilled 상태의 Promise 가 반환되는 것을 아실 것입니다.

 

Promise {<fulfilled>: undefined}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: undefined

 

해당 내용은 아래 강좌에서 다루었습니다.

강좌에서는 then() 메서드에서 Promise 를 반환하는 경우만 다루었는데요. 아래와 같이 일반 값을 반환하면 어떻게 될까요?

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return value * 2;
})

 

이 때에도 fulfilled 상태의 Promise 가 반환됩니다.

 

Promise {<fulfilled>: 20}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: 20

 

Promise 가 아닌 값을 반환했는데 Promise 가 반환됩니다. 이 것은 then() 메서드에서 일반 값을 반환할 경우, 자동으로 Promise.resolve 로 한 번 감싸기 때문입니다. 즉, 아래와 같은 것이죠.

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return Promise.resolve(value * 2);
})

 

그래서 아래와 같이 계속 then() 메서드를 체이닝해서 사용할 수 있습니다.

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return Promise.resolve(value * 2);
})
.then((value) => console.log(`값이 도착했습니다. ${value}`))

아무것도 반환하지 않을 경우는 어떨까요? 자바스크립트에서 함수가 아무 것도 반환하지 않더라도 undefined 를 반환합니다.

 

function a() {}
a() // undefined 를 반환합니다. 

 

그래서 아래와 같이 then 메서드에서 아무 것도 반환하지 않더라도

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	console.log(value);
})

 

undefined 가 반환되고 then 에서 반환된 값은 자동으로 Promise.resolve() 로 감싸집니다.

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	console.log(value);
	return Promise.resolve();
})

 

위 코드처럼 실행됩니다. 그래서 반환값으로 undefined 를 PromiseResult 로 갖는 fulfilled 상태의 Promise 가 반환되는 것입니다.

 

Promise {<fulfilled>: undefined}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: undefined

 

만약 일반 값이 아닌 Error 를 throw 하면 어떻게 될까요?

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	throw new Error('오류가 발생했습니다');
})

 

이 때는 상태가 'rejected' 인 Promise 가 반환됩니다.

 

Promise {<rejected>: Error: 오류가 발생했습니다.}
	[[Prototype]]: Promise
	[[PromiseState]]: "rejected"
	[[PromiseResult]]: Error: 오류가 발생했습니다.

 

즉, 일반값을 반환하거나 오류를 throw 하면 자동으로 Promise 로 감싸지는 것을 알 수 있습니다. 그렇기 때문에 Promise 의 메서드인 then 또는 catch 를 체이닝을 통해서 사용할 수 있는 것입니다.

자 그럼 새로운 Promise 를 반환할 때는 언제인가요? 새로운 비동기 작업이 필요할 때입니다. 그런데 이 때, 주의 깊게 생각해 볼 문제가 있습니다.

만약 Promise 를 반환하는데 일반값이나 오류 throw 일 때처럼 Promise 로 감싸면 어떻게 될까요?

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => resolve(value * 2), 1000);
	});
})

 

 

만약 then 메서드가 반환하는 Promise 를 다시 Promise 로 감싼다면, Promise<Promise<Value, Error>, Error> 처럼 될 것입니다. 즉, 배열의 배열처럼 [array, [of, [array]]] Promise 가 계속 쌓이게 되는 것입니다.

그렇게 되면 다음 then() 에서 아래와 같이 사용해야 할 것 입니다.

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => resolve(value * 2), 1000);
	});
})
.then((if_promise) => {
	if_promise.then(value => {
		console.log(value);
	})
})

 

하지만 뭔가 이상하지 않나요? 네 그렇습니다. 만약 Promise 를 위처럼 사용해야 한다면 '콜백헬'에서 발생했던, 깊이가 깊어지는 문제가 다시 발생합니다.

그래서 Promise 는 [array, [of, [array]]] 의 flat() 메서드처럼 중첩된 Promise 를 flat 하게 펴주는 기능을 자동으로 제공합니다.

 

['array', ['of', ['array']]].flat(Infinity)
// 출력결과
['array', 'of', 'array']

 

그래서 아래와 같이 then() 메서드에서 Promise 를 반환하면,

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => resolve(value * 2), 1000);
	});
})

 

Promise<Promise<Value, Error>, Error> 가 flat 되어 Promise<Value, Error> 가 반환됩니다. 그래서 결과가 아래와 같죠.

 

Promise {<pending>}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: 20

 

PromiseResult 로 값은 20이 있지만, Promise 가 pending 상태에 있기 때문에, then 을 통해서 값을 얻어야 합니다.

 

new Promise((resolve, reject) => {
	resolve(10);
})
.then((value) => {
	return new Promise((resolve, reject) => {
		setTimeout(() => resolve(value * 2), 1000);
	});
})
.then((value) => console.log(value));

 

그러면, 1초 뒤에 콘솔에 '20' 이 출력됩니다. 따라서 정리해 보면, Promise 는 then 메서드나 catch 메서드에서 일반값을 반환하거나 Error 를 throw 하면 Promise 로 자동으로 감싸서 반환합니다. 그래서 then 이나 catch 를 사용해서 계속 체이닝을 이어갈 수 있습니다.

그리고 then 메서드나 catch 메서드에서 Promise 를 반환하면 자동으로 flat 을 실행해 중첩이 없는 Promise 를 반환합니다. 그래서 콜백헬에서 나타나는 깊이가 깊어지는 문제를 해결합니다.

여기에서 잠깐 나온 Promise 를 반환하면 자동으로 flat 을 실행해 중첩이 없는 Promise 를 반환하는 내용은 모나드의 map 과 flatMap 과 비슷합니다. 즉, Promise 의 then 이 map 연산자와 비슷하면서도 반환값에 대해서는 flatMap 기능을 수행해 주는 것과 비슷합니다.

참고로, 모나드에 대한 내용은 Result 타입과 모나드 강좌에서 다루고 있습니다.

답변이 도움 되었기를 바랍니다. 감사합니다.

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. Droid님께서 질문하신 Promise 체이닝과 관련하여 설명을 드리겠습니다.

자바스크립트의 Promise는 비동기 작업을 처리하기 위해 사용되며, thencatch 메서드를 통해 작업의 결과에 따라 후속 작업을 정의할 수 있습니다. 여기서 핵심은 then 메서드가 또 다른 Promise를 반환함으로써 체이닝이 가능하다는 점입니다.

  1. Promise 체이닝: then 메서드는 항상 Promise 객체를 반환합니다. 만약 then 메서드의 콜백 함수 내에서 새로운 Promise 객체를 반환하게 되면, 그 다음에 체이닝된 then 메서드는 새로운 Promise가 처리될 때까지 기다리게 됩니다. 즉, then에서 반환된 Promise의 이행(resolve) 또는 거부(reject)를 기다렸다가 그 다음 콜백을 실행하게 됩니다.

  2. 비동기 실행: then 메서드에 전달된 콜백 함수는 현재 콜스택이 비어질 때 이벤트 루프에 의해 실행됩니다. 이는 마이크로태스크 큐에 등록되어 기다리게 되고, 자바스크립트의 비동기 처리 모델에 따라 콜스택의 모든 실행이 마무리된 후에 실행됩니다.

  3. 코드 흐름 설명:

    • 첫 번째 then에서 1이라는 값이 콘솔에 출력됩니다. 이곳에서 반환된 새로운 Promise2초 후에 이행될 때까지 기다립니다.
    • 두 번째 then에서 2라는 값이 콘솔에 출력되지만, 이 Promise는 1초 후 강제로 거부(reject)됩니다.
    • 마지막으로, 체이닝된 catch 블록은 거부된 Promise의 오류를 처리하게 되어 콘솔에 에러 메시지가 출력됩니다.

관련 내용을 더 알아보고 싶으시다면, 자바스크립트의 프로미스 체이닝과 이벤트 루프에 대해 참고할 수 있는 강의를 추천드립니다:
- ProJavaScript 시리즈 - 프로미스 체이닝과 마이크로태스크 큐 이해하기

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

Droid님의 프로필 이미지

작성한 질문수

질문하기