[JavaScript] 프로미스(Promise)

Dodam·2023년 9월 11일
0

[JavaScript]

목록 보기
8/10
post-thumbnail

프로미스(Promise)

자바스크립트는 비동기 처리를 위해 콜백함수를 사용하는데, 콜백을 남용하게 되면 콜백 지옥에 빠질 수 있다.
또한 에러 처리가 힘들고, 여러 개의 비동기 처리를 한 번에 하는데 한계가 있다.
이런 콜백 함수의 단점을 보완하며 비동기 처리에 사용되는 객체를 프로미스(Promise)라고 한다.

프로미스를 사용하면 다음과 같은 이점을 얻을 수 있다.

  • 비동기 처리 시점을 명확하게 표현할 수 있다.
  • 연속된 비동기 처리 작업을 수정, 삭제, 추가하기 편하고 유연하다.
  • 비동기 작업 상태를 쉽게 확인할 수 있다.
  • 코드의 유지 보수성이 증가한다.

프로미스는 객체이기 때문에 생성자 함수를 호출하여 인스턴스화할 수 있다.
프로미스의 생성자 함수는 resolve와 reject 함수를 인자로 전달받는 콜백 함수를 인자로 전달 받는다.
프로미스는 인자로 전달받은 콜백 함수를 내부에서 비동기 처리한다.

Promise는 비동기 처리가 성공(fulfilled) 또는 실패(rejected) 등의 상태 정보를 갖게 된다.
resolve 함수가 호출 된 경우 성공 상태이고, reject 함수가 호출된 경우 실패 상태이다.

다음 예제를 통해 살펴보면

promise라는 이름의 전역 변수에 프로미스를 할당하였고, 그 안에는 1+1 이 2라면 resolve 함수를 호출하고, 아니면 reject 함수를 호출하도록 구현하였다.
그 다음, 프로미스의 후속 처리 메소드then(), catch()를 통해 비동기 처리 결과 메세지를 전달받아 처리하였다.

const promise = () => new Promise((resolve, reject) => {
	let a = 1 + 1
    
    if (a == 2) {
    	resolve('success')
    } else {
    	reject('failed')
    }
})

promise().then((message) => {
	console.log('This is in the then ' + message)
}).catch((message) => {
	console.log('This is in the catch ' + message)
})

후속 처리 메소드

프로미스로 구현된 비동기 함수를 호출하는 측에서는 프로미스 객체의 후속 처리 메소드(then, catch)를 통해 비동기 처리 결과 또는 에러 메세지를 전달받아 처리한다.

then

  • then 메소드는 두 개의 콜백 함수를 인자로 전달 받는다.
  • 첫 번째 콜백 함수는 성공(fulfilled, resolve 함수가 호출된 경우)시에 실행된다.
  • 두 번째 콜백 함수는 실패(rejected, reject 함수가 호출된 경우)시에 실행된다.
  • then 메소드는 기본적으로 프로미스를 반환한다.

catch

  • catch 메소드는 비동기 처리 혹은 then 메소드 실행 중 발생한 에러(예외)가 발생하면 호출된다.
  • catch 메소드 역시 프로미스를 반환한다.

프로미스의 에러 처리 방법

프로미스를 이용한 에러 처리 방법은 두 가지가 있다.

첫 번째 방법은 then 메소드의 두 번째 인자로 예외를 처리하는 방법이다.

const promise = () => new Promise(() => {
	let a = 1 + 1
    
    if (a == 3) {
    	resolve('success')
    } else {
    	reject('failed')
    }
})

promise().then((message) => {
	console.log('This is in the then ' + message)
}, (error) => {
	console.log('This is in the catch ' + error)
})
This is in the then success
This is in the catch Error: failed

따라서 프로미스의 에러를 처리할 때에는 더 많은 상황의 예외를 처리할 수 있는 catch 메소드를 사용하는 것이 좋다.

프로미스 체이닝(Promise Chaining )

비동기 함수의 결과를 가지고 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩되어 콜백 지옥이 발생할 수 있다.
프로미스는 후속 처리 메소드를 체이닝하여 프로미스를 반환하는 여러 개의 비동기 함수들을 연결하여 사용할 수 있다.

다음 예제를 통해 살펴보면

첫 번째로 호출한 비동기 함수(promise)의 결괏값을 then 후속 처리 메소드를 통해 두 번째 비동기 함수로 전달하였고, 그 결괏값을 메세지로 출력하게 된다.

const promise = (result) => {
	return new Promise((resolve, reject) => {
    	if (result == 'success')
          resolve('success')
        else
          reject('failed')
    })
}

promise('success')
	.then(promise)	// .then(result => promise(result))
	.then(message => console.log('This is in the then ' + message))
	.catch(error => console.log('This is in the catch ' + error))
This is in the then success

정적 메소드

프로미스는 5가지 정적 메소드를 제공한다.
정적 메소드이기 때문에 객체의 생성없이 사용 가능하다.

Promise.resolve

Promise.resolve 메소드는 인자값을 래핑하여 프로미스를 반환한다. (fulfilled)

Promise.reject

Promise.reject 메소드 역시 인자값을 래핑하여 프로미스를 반환한다. (rejected)

Promise.all

Promise.all 메소드는 프로미스가 담겨있는 배열과 같은 이터러블 객체를 인자로 받는다.
인자로 전달받은 모든 프로미스를 병렬로 처리하고, 그 결괏값을 배열에 담아 resolve로 반환한다.

만약 여러 개의 프로미스를 순차적으로 처리한다면 어떻게 될까?

첫 번째 프로미스가 종료된 후에 두 번째 프로미스가 실행되고, 두 번째 프로미스가 종료된 후에 세 번째 프로미스가 실행된다.
물론, 프로미스가 다른 프로미스를 의존하고 있는 경우에는 순차적으로 처리할 필요가 있지만 다음의 경우에는 서로 의존하고 있지 않기 때문에 순차적으로 처리할 필요가 없다.

const promise1 = () => new Promise(resolve => setTimeout(() => resolve(1), 1000))
const promise2 = () => new Promise(resolve => setTimeout(() => resolve(2), 2000)
const promise3 = () => new Promise(resolve => setTimeout(() => resolve(3), 3000)
                                   
promise1().then(result => {
	console.log(result)	// 프로그램을 실행하고 1초 뒤에 수행
  	return promise2()
}).then(result => {
	console.log(result)	// 프로그램을 실행하고 3초 뒤에 수행 (1 + 2)
  	return promise3()
}).then(result => {
	console.log(result)	// 프로그램을 실행하고 6초 뒤에 수행 (1 + 2 + 3)
})                                   
1
2
3

따라서 서로 의존관계가 아닌 여러 프로미스들을 이터러블 객체에 담아 Promise.all 메소드를 이용해 한 번에 병렬처리 할 수 있다.
가장 마지막으로 끝나는 프로미스를 기준으로 수행되고, 모든 프로미스가 fulfilled 상태가 되면 결괏값을 배열에 담아 새로운 프로미스를 반환한다.
프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료한다.

Promise.all([
	new Promise(resolve => setTimeout(() => resolve(1), 1000)),
  	new Promise(resolve => setTimeout(() => resolve(2), 2000)),
  	new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log)	// 프로그램을 실행하고 3초 뒤에 수행
.catch(console.log)
[ 1, 2, 3 ]

Promise.rece

Promise.race 메소드는 Promise.all 메소드와 동일하게 프로미스가 담겨있는 이터러블 객체를 인자로 받지만, Promise.all과 달리 병렬로 처리하지 않고 가장 먼저 끝나는 프로미스의 결괏값을 resolve로 반환한다.

Promise.race([
    new Promise(resolve => setTimeout(() => resolve(1), 1000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
    new Promise(resolve => setTimeout(() => resolve(3), 3000))
]).then(console.log) 
.catch(console.log)
1

Promise.allSettled

Promise.allSettled 메소드 역시 Promise.all 메소드와 동일하게 프로미스가 담겨있는 이터러블 객체를 인자로 받고 병렬로 처리한다.
다만 Promise.all의 경우, 프로미스를 수행하던 도중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료하게 되지만,
Promise.allSettled 메소드의 경우 rejected 상태가 되어도 수행을 종료하지 않고, 프로미스가 수행된 상태와 결괏값을 배열에 담아 resolve로 반환한다.

Promise.allSettled([
	new Promise(resolve => setTimeout(() => resolve(1), 1000)),
  	new Promise((resolve, rejected) => setTimeout(() => rejected(2), 2000))
]).then(console.log)
[
  { status: 'fulfilled', value: 1 },
  { status: 'rejected', reason: 2 }
]

Promise.all 메소드와 또 다른 차이점은 각각의 프로미스 처리 결과를 객체로 나타내고, status 프로퍼티를 가지게 된다.
fullfilled 상태인 경우 value 프로퍼티를 가지게 되고, rejected 상태인 경우 reason 프로퍼티를 가지게 된다.

profile
Good things take time

0개의 댓글