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

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

Droid님의 프로필 이미지
Droid

작성한 질문수

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

Task Queue의 우선순위 📕

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

해결된 질문

작성

·

29

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 인턴입니다. 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님의 프로필 이미지
Droid

작성한 질문수

질문하기