본문 바로가기

프로그래밍/js

ES6 lessons 9. 프로미스

2023.04.02

자바스크립트 스터디 11회차

공부 사이트: https://poiemaweb.com/

 

 

 

9. 프로미스

비동기 처리 시 콜백 함수를 사용하는데, 콜백 함수는 여러가지 단점을 가지고 있다. 

이러한 콜백 패턴 대신 사용하는 객체가 프로미스이다.

 

1) 콜백 패턴의 단점

① 콜백 헬

비동기 처리 모델은 실행 완료를 기다리지 않고 다음 작업을 즉시 실행한다.

비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수 호출이 중첩되어 복잡도가 높아지는 현상이 콜백 헬이다.

콜백 헬은 가독성을 낮추고, 복잡도를 증가시켜 실수를 유발하는 원인이 되며, 에러 처리가 곤란하다.

 

② 에러 처리

try {
   setTimeout(()=> { throw new Error('Error!'); }, 1000);
} catch(e) {
    console.log('error catch!');
    console.log(e);
}

1초 후에 콜백 함수가 실행되고, 의도적으로 예외를 발생시킨다. 그러나 catch문에서 에러가 잡히지는 않는다.

 

[흐름]

- 비동기 함수인 setTimeout()는 콜백 함수가 실행될 때까지 기다리지 않고 즉시 종료 (콜 스택에서 제거)

- setTimneout()의 콜백함수는 즉시 실행되지 않고 지정 대기 시간만큼 기다리다가, 이벤트 발생 시 태스크 큐로 이동

- 콜백함수는 콜 스택이 비워졌을 때 콜 스택으로 이동

 

콜백함수가 실행될 때, setTimeout() 은 콜 스택에서 제거된 상태이다. 이는 콜백함수의 호출자가 setTimeout()이 아니라는 것을 의미한다. 

예외는 호출자 방향으로 전파되는데, 호출자가 존재하지 않으므로 catch문에서 캐치되지 않은 것이다.

 

이러한 문제를 해결하기 위해 프로미스가 등장했다.

 

 

2) 프로미스

Promise 생성자 함수를 통해 인스턴스화된다.

이 생성자 함수는 비동기 작업을 수행할 콜백 함수를 인자로 전달받는데, 콜백함수는 resolve와 reject 함수를 인지로 전달받는다.

const promise = new Promise((resolve, reject) => {
    if(/*비동기 작업 수행 성공 시*/) {
        resolve('result');
    }
    else {
        reject('faulure reason');
    }
});

 

[Promise의 상태]

pending 비동기 처리가 수행되지 않은 상태 not(resolve or reject)
fulfilled 비동기 처리 수행 (성공) resolve 함수 호출된 상태
rejected 비동기 처리 수행 (실패) reject 함수 호출된 상태
settled 비동기 처리 수행 (성공 or 실패) resolve or reject

 

[example] Promise 예제 

<!DOCTYPE html>
<html>
<body>
	<pre class="result"></pre>
	<script>
    const $result = document.querySelector('.result');
    const render = content => { $result.textContent = JSON.stringify(content, null, 2); };
	
	const promiseAjax = (method, url, payload) =>{
		return new Promise((resolve, reject) =>{
			const xhr = new XMLHttpRequest();
			xhr.open(method, url);
			xhr.setRequestHeader('Content-type', 'application/json');
			xhr.send(JSON.stringify(payload));
			
			xhr.onreadystatechange = function(){
				if (xhr.readyState !== XMLHttpRequest.DONE) return;
				if (xhr.status >= 200 && xhr. status < 400) {
					resolve(xhr.response);
				}else{
					reject(new Error(xhr.status));
				}
			};
		});
	};
	
	promiseAjax('GET','http://jsonplaceholder.typicode.com/posts/1')
		.then(JSON.parse)
		.then(render, console.error);
	
	</script>
</body>
</html>

Promise 생성자 함수가 전달받은 콜백 함수는 내부에서 비동기 처리 작업을 수행한다.

`lesson 34. Ajax` 에서 배운 `XMLHttpRequest 객체를 이용한 Ajax 요청 생성` 순서대로 진행한다.

비동기 처리에 성공하면 resolve 메소드의 인자로 Ajax 요청의 response 값을 전달하고, 실패하면 reject 메소드의 인자로 에러 메시지를 전달한다.

전달한 값은 Promise 객체의 후속 처리 메소드로 전달된다.

 

 

3) 프로미스 후속 처리 메소드

Promise 객체가 갖는 상태에 따라 후속 처리 메소드를 체이닝 방식으로 호출한다.

후속 처리 메소드는 아래와 같다.

 

① then

- 두 개의 콜백 함수를 인자로 전달받는다. 첫 번째 콜백 함수는 성공 시 호출, 두 번째 콜백 함수는 실패 시 호출된다.

- 반환 값은 Promise 객체이다.

- ★ 여러개 사용할 수 있다.

- 첫 번쨰 then()은 Promise가 해결되면 적용할 함수, 두 번째 then()은 첫 번째 then() 메소드에서 반환된 Promise가 해결되면 적용할 함수 ‥ 이런식의 체이닝 방식으로 호출된다.

promiseFunction()
  .then(function(result) {
    // 첫 번째 단계에서 적용할 함수
    return result * 2;
  })
  .then(function(result) {
    // 두 번째 단계에서 적용할 함수
    return result + 5;
  })
  .then(function(result) {
    // 세 번째 단계에서 적용할 함수
    console.log(result);
  })
  .catch(function(error) {
    // Promise 체인 중에 발생한 오류를 처리하는 함수
    console.error(error);
  });

 

② catch

- 예외가 발생하면 호출된다. 

- 반환 값은 Promise 객체이다.

 

[example] Promise 예제 코드를 보면 then() 이 두 번 사용되었다.

ㅇ 첫번째 then() : JSON.parse()을 사용하여 응답 데이터를 JSON으로 파싱

ㅇ 두 번째 then() : 첫 번쨰 then()의 결과를 인수로 하여 render() 함수를 호출한다. 만약 Promise가 실패했다면 console.error() 함수를 호출하여 에러를 출력한다.

 

 

 

4) 프로미스 에러 처리

비동기 처리 시 발생한 에러는 then 메소드의 두 번째 콜백 함수로 처리하거나, 후속 처리 메소드 catch를 사용해서 처리할 수 있다.

// then 메소드의 두 번째 인자로 예외 처리
promiseAjax(wrongUrl)
  .then(res => console.log(res), err => console.error(err));
  
  
// 후속 처리 메소드로 예외 처리
promiseAjax(wrongUrl)
  .then(res => console.log(res))
  .catch(err => console.error(err));

 

 

then 메소드를 이용한 예외 처리는 가독성이 좋지 않다.

모든 then 메소드 호출 이후에 catch 메소드를 사용하는 것이 비동기 처리에서 발생한 에러 뿐만아니라 then 메소드 내부의 에러까지 잡아주기에, catch 메소드 사용을 권장한다.

 

 

5) 프로미스의 정적 메소드

Promise는 주로 생성자 함수로 사용되나, 함수 또한 객체이므로 메소드를 가질 수 있다.

Promise 객체는 4개의 정적 메소드를 제공한다.

 

① Promise.resolve / Promise.reject

 

② Promise.all

- 프로미스가 담겨있는 배열 등의 이터러블을 인자로 전달받아 병렬로 처리한 후, 처리 결과를 resolve하는 새로운 프로미스로 반환한다.

- 처리 순서의 보장이 필요할 때 사용한다.

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

첫 번째 프로미스가 가장 나중에 실행되어도, 첫 번째 프로미스가 resolve한 처리 결과부터 차례대로 배열에 담아서 그 배열을 resolve하는 새로운 프로미스를 반환한다.

프로미스 처리가 하나라도 실패하면, 가장 먼저 실패한 프로미스가 reject한 에러를 reject하는 새로운 프로미스를 즉시 반환한다.

 

 

③ Promise.race

- 프로미스가 담겨있는 이터러블을 인자로 전달받아 가장 먼저 처리된 프로미스가 resolve한 결과를 resolve하는 새로운 프로미스를 반환한다.

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

위 코드에서 가장 먼저 처리되는 프로미스는 세 번째 프로미스이므로, 3이 반환된다.

Promise.all과 같이, 에러 발생 시 실패한 프로미스가 reject한 에러를 reject하는 새로운 프로미스를 반환한다.

 

 

'프로그래밍 > js' 카테고리의 다른 글

ES lessons 11. 이터레이션 & for of문  (0) 2023.04.05
ES6 lessons 10. 심볼  (0) 2023.04.03
ES6 lessons 8. 모듈  (0) 2023.04.03
ES6 lessons 7. 클래스  (0) 2023.03.28
ES6 lessons. 6 Destructuring  (0) 2023.03.28