"프로미스는 나중에 채울 빈칸이 아니라, 이미 시작된 작업의 성공이나 실패를 지켜보는 손잡이야."
프로미스의 세 가지 상태
프로미스는 대기 중(pending), 성공(fulfilled), 실패(rejected) 가운데 한 상태에 있어. 작업이 끝나기 전에는 대기 중이고, 끝난 뒤에는 값과 함께 성공하거나 오류 이유와 함께 실패해.
상태 변화는 한 번뿐이야. 대기 중이던 프로미스가 성공이나 실패로 정해지면 다시 돌아가거나 결과를 바꾸지 않아. 이 성질 덕분에 같은 프로미스를 여러 곳에서 지켜봐도 모두 같은 완료 결과를 받게 돼.
결과에 반응하는 세 메서드
.then은 성공했을 때, .catch는 실패했을 때, .finally는 어느 쪽이든 작업이 끝났을 때 실행할 처리를 붙여:
fetchUser(id)
.then(user => user.email)
.then(email => sendWelcome(email))
.catch(err => logError(err))
.finally(() => closeDB())
이 메서드들은 원래 프로미스를 바꾸지 않고 새 프로미스를 돌려줘. .then의 처리 함수가 일반 값을 반환하면 다음 프로미스는 그 값으로 성공해. 다른 프로미스를 반환하면 그 작업이 끝날 때까지 기다리고, 오류를 던지면 다음 프로미스는 실패해. 이렇게 앞 단계의 결과가 다음 단계로 자연스럽게 이어져.
오류는 처리하는 지점까지 이어져
.catch로 이동해. 콜백마다 if (err)를 반복하지 않아도 되는 이유야. 체인의 끝에 둔 .catch 하나가 앞 단계 어느 곳에서 난 오류든 받을 수 있어.반대로 실패를 아무도 받지 않으면 처리되지 않은 프로미스 거부가 돼. Node 15부터 기본 설정에서는 처리되지 않은 거부가 잡히지 않은 예외로 바뀌어 프로세스를 끝내.
process.on('unhandledRejection', ...)은 마지막 기록과 정리를 위한 안전망으로 둘 수 있지만, 거기서 정상 실행을 회복하려고 해서는 안 돼. 각 체인을 .catch로 마무리하거나, async 함수 안에서 try/catch로 실패의 책임을 분명히 해.여러 프로미스를 함께 기다리는 방법
Promise.all은 모두 성공해야 할 때 써. 하나라도 실패하면 곧바로 실패해.Promise.allSettled는 전부 끝날 때까지 기다린 뒤 각 작업의 성공과 실패를 함께 돌려줘.Promise.race는 성공이든 실패든 가장 먼저 끝난 결과를 따라가.Promise.any는 가장 먼저 성공한 값을 돌려주고, 전부 실패했을 때만 실패해.
어느 방법이 맞는지는 “한 작업의 실패가 전체를 멈춰야 하는가?”라는 질문으로 고르면 돼. 필요한 실패 정책을 먼저 정하면 메서드는 자연스럽게 따라와.
직접 프로미스를 만들 일은 많지 않아
요즘 Node 표준 라이브러리는 fs/promises, dns/promises, timers/promises처럼 프로미스를 바로 돌려주는 API를 제공해. 예전 콜백 API는 util.promisify로 감쌀 수도 있어. 그래서 new Promise(...)는 이벤트 기반 API를 한 번의 완료 결과로 바꾸거나, 기존 도구가 없는 경계를 연결할 때 주로 사용해.
프로미스를 직접 만드는 코드가 자주 반복된다면 표준 라이브러리나 이미 검증된 도우미가 같은 일을 제공하는지 먼저 확인해. 프로미스 생성자 안에서 예외와 취소, 중복 완료를 모두 다루는 일은 생각보다 까다로워.
Pippa의 고백
const p = doThing()에서 작업은 호출과 함께 시작되고, p는 그 결과를 붙잡는 손잡이야. 이 구분을 이해하자 연속된 .then도 명령 목록이 아니라 앞선 결과에 대한 반응으로 보이기 시작했어.