프로미스

2023. 4. 3. 17:59js


자바스크립트 비동기 처리 패턴으로 콜백함수를 사용함.

이때 전통적인 콜백 패턴에서 콜백 헬로 인한 가독성과 
에러 처리의 불편함으로 인해 다른 패턴으로 
프로미스를 도입하였음.

프로미스의 경우 전통적인 콜백 패턴이 가진 단점을 보완 하며 시점을 
명확하게 표현할 수 있다는 장점이 있다.

자바스크립트에서 promise 객체는 아직 사용하지 않은 값을 표현하며,
값은 비동기로 처리된다. 이때 대기, 이행, 거부, 3가지 상태를 가지고 있으며
처리 결과에 따라 변경된다. 기본값으로 대기 상태이다.

45.1 비동기 처리를 위한 콜백 패턴의 단점.

45.1.1 콜백 헬

get 함수의 경우 비동기 함수로 이를 호출 하면 내부의 비동기로 동작하는 코드가
완료되지 않았어도 즉시 종료된다. 따라서 비동기 함수 내부의 비동기 코드는
함수가 종료되고 실행된다. 따라서 이를 외부로 반환할때 원하는대로 동작 하지 않을 수 있다.

get 함수가 호출 되면 XMLHttpRequest객체를 생성하고 HTTP요청을 
초기화 한 후 HTTP 요청을 전송한다.

xhr.onload 이벤트 핸들러 프로퍼티에 이벤트 핸들러를 바인딩 하고 종료 한다.
이때 onload의 경우 함수가 끝나고 실행 되기 때문에 여기서 반환을 하더라도
상위 스코프에 값은 undefined가 된다 .

비슷하게 상위스코프의 변수에 값을 할당하고 결과 확인을 해도 
동일한 결과가 나오는데 이는 앞장에서 살펴본 값의 평가 
순서에 따라 그런것임을 알 수 있다. 이러한 경우들에서 캐치에 대한 필요성이
대두 된다고 할 수 있다.

이처럼 콜백 함수를 통해 비동기 처리 결과에 대한 후속 처리를 수행하는 비동기 함수가 
비동기 처리 결과를 가지고 다시 호출하는것을 콜백헬 이라고 함.

45.1.2 에러 처리의 한계

비동기 처리를 위한 콜백 패턴의 문제점 중에서 가장 심각한것은 에러처리가 
곤란하다는 것이다. 

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


에러는 호출자 방향으로 전파된다. 콜 스택의 아래방향 으로 전파된다. 
setTimeout함수의 콜백함수를 호출한 setTimeout 함수의 콜백함수가 
발생시킨 에러는 catch블록에서 캐치되지 않는다.

이런 문제들을 극복 하기 위해서 promise가 도입되었다

45.2 프로미스의 생성
Promise 생성자 함수를 new 연산자와 함께 호출하면 프로미스 객체를 
생성한다. 생성자 함수는 비동기 처리르 수행할 콜백 함수를 인수로 전달
받고 resolve 와 reject 함수를 인수로 전달받는다.

const promiseGet = url => {
	return new Promise((resolve, reject) => {
		const xhr = new XMLHttpRequest();
		xhr.open("GET", url);
		xhr.send();
		if (xhr.status === 200) {
			resolve(JSON.parse(xhr.response));
		} else {
			reject(new Error(xhr.status));
		}	
	});
}

promiseGet("https://jsonplaceholder.typicode.com/posts/1");



프로미스의 상태는 resolve 또는 reject 를 호출 하는것으로 결정된다.
fulfilled 또는 rejected 상태를 settled상태라고 함. 이는
pending이 아닌 상태로 무언가 처리된 상태를 의미한다.

비동기 처리가 성공하면 프로미스는 pending에서 fulfilled상태로 
변화한다. 그리고 결과값으로 1을 가지게 됨.

45.3 프로미스의 후속 처리 메서드

프로미스가 fulfilled 상태가 되면 처리결과를 가지고 무언갈 하고,
rejected 상태가 되면 error처리를 해야한다.

프로미스의 비동기 처리 상태가 변화되면 후속 처리 메서드에
인수로 전달한 호출된다. 

45.3.1 Promise.prototype.then

then 메서드는 두 개의 콜백 함수를 인수로 전달받는다.
fulfilled,
rejected 두가지 상태에 해당하는 프로미스를 받음.

then 메서드는 언제나 프로미스를 그대로 반환하고 콜백 함수가 
아닌 값을 반환하면 그값을 암묵적으로 resolve 또는 reject하여 프로미스를
생성하여 반환한다.

45.3.2 Promise.prototype.catch

catch 메서드가 한 개의 콜백 함수를 인수로 전달받는다.
catch 메서드의 콜백 함수는 프로미스가 rejected상태인 경우만 
호출된다.

new Promise((_, reject) => 
	reject(new Error(`rejected`)))
    .catch(e => console.log(e));


catch 메서드는 then과 동일하게 동작.

45.3.3 Promise.prototype.finally

finally 메서드는 한개의 콜백 함수로 인수로 전달 받는다. 
메서드의 콜백 함수는 프로미스의 성공과 실패 관계없이
무조건 한 번 호출된다. 이또한 프로미스 객체를 반환한다.

45.4 프로미스의 에러 처리

비동기 처리를 위한 콜백 패턴은 에러처리가 곤란하다는 문제가 있다.

이때 프로미스의 경우 에러를 문제없이 처리 할 수 있다.
비동기 처리 결과에 대한 후속 처리는 프로미스가 제공하는 후속 처리 
메서드를 사용하여 수행한다. 비동기 처리에서 발생한 에러는
then 메서드의 두 번째 콜백 함수로 처리할 수 있다.

45.5 프로미스 체이닝

45.1.1절 "콜백 헬"에서 살펴보았듯 비동기 처리를 위한 
프로미스는 then, catch, finally 후속 처리 메서드를 통해 전달한다.

const url = "https://jsonplaceholder.typicode.com"

promiseGet(`${url}/posts/1`)
	.then(({userId}) => promiseGet(`${url}/users/${userId}`))
	.then(userInfo => console.log(userInfo))
	.catch(err => console.error(err));


then->then->catch와 같은 방식으로 후속 처리 메서드를 호출했다.
이를 프로미스 체이닝 이라 한다.

후속 처리 메서드    콜백 함수의 인수  후속 처리 메서드의 반환값
then  promiseGet 콜백 함수가 반환한 프로미스
함수 반환의
resolve 값

then  첫번째 then 메서드  콜백 함수가 반환한 값을
가 반환한 resolve값 resolve한 프로미스

catch  promiseGet 함수 또는 콜백 함수가 반환한 값을
앞선 후속 처리 메서드가 resolve한 프로미스
반환한 프로미스가
reject 한 값.

콜백 패턴 가독성이 좋지 않다. es8에 도입된 async/await을 통해
해결 할 수 있다. async/await을 사용하면 후속 처리 메서드 없이 반환하도록
구현 가능하다 

const url = "https://jsonplaceholder.typicode.com";

(async () => {
	const {userId} = await promiseGet(`${url}/posts/1`);
	const userInfo = await promiseGet(`${url}/${userId});
	console.log(userInfo);
});


45.6 프로미스의 정적 메서드

45.6.1 promise.resolve / promise.reject
promise.resolve 메서드는 인수로 전달받은 값을 resolve하는
프로미스를 생성한다.

45.6.2 Promise.all

여러개의 비동기 처리를 모두 병렬 처리할 때 사용한다.

const requestData1 = () => 
	new Promise(resolve => setTimeout(() => resolve(1), 3000));

const requestData2 = () => 
	new Promise(resolve => setTimeout(() => resolve(2), 2000));

const requestData3 = () => 
	new Promise(resolve => setTimeout(() => resolve(3), 1000));

const res = [];

requestData1()
	.then(data => {
		res.push(data);
		return (requestData2());
	})
	.then(data => {
		res.push(data);
		return (requestData3());
	})
	.then(data => {
		res.push(data);
		console.log(res);
	})
	.catch(console.error);



세개의 비동기 처리를 순차적으로 처리한다.
위 예제의 경우 세개의 비동기 처리는 서로 의존하지 않고 
개별적으로 수행한다.

promise.all 메서드는 여러 개의 비동기 처리를 모두 
병렬 처리할 때 사용한다.

const requestData1 = () => 
	new Promise(resolve => setTimeout(() => resolve(1), 3000));

const requestData2 = () => 
	new Promise(resolve => setTimeout(() => resolve(2), 2000));

const requestData3 = () => 
	new Promise(resolve => setTimeout(() => resolve(3), 1000));



promise.all 메서드는 프로미스 요소를 갖는 배열등의 이터러블
인수로 전달한다. 모두 fulfilled상태가 되면
모든 처리 결과를 배열에 저장하여 반환.

promise.all 메서드는 인수로 전달받은 배열의 하나라도
rejected상태가 되면 즉시 종료한다.

promise.all([
	new Promise((_, reject) => setTimeout(() => reject(new Error("Error 1")), 3000)),
	new Promise((_, reject) => setTimeout(() => reject(new Error("Error 1")), 2000)),
	new Promise((_, reject) => setTimeout(() => reject(new Error("Error 1")), 1000)),
])
.then(console.log)
.catch(console.log)



45.6.3 promise.race

메서드와 동일하게 프로미스를 요소로 갖는 배열등의 이터러블을 
인수로 전달받는다. promise.all메서드 처럼 모든
프로미스가 fulfilled 상태가 된 결과를 resolve하는 
새로운 프로세스 반환

45.6.4 

promise.allSettled([
	new Promise(resolve => setTimeout(() => resolve(1), 3000)),
	new Promise((_, reject) => setTimeout(() => reject(new Error("Error 1")), 2000)),
])


45.7 마이크로태스크 큐

프로미스 후속 처리 메서드 비동기 동작 1->2->3 순으로 출력될 것 같지만
2->3->1 로 저장되기 때문이다.

마이크로 태스크 큐는 태스크 큐보다 우선순위가 높다.

45.8 fetch

fetch 함수는 XMLHttpRequest 객체와 마찬가지로 HTTP요청
전송기능으로 제공하는 클라이언트 사이드 api이다.

fetch 함수는 http응답을 나타내는 response객체를 래핑한
반환.

이를 통해 요청을 전송할 URL과 두번째 인수로 HTTP 요청 메서드, 
요청 헤더, 페이로드 등을 설정한 객체를 전달한다.

1. GET 요청

request.get("https://jsonplaceholder.typicode.com/todos/1")
	.then(response => response.json())
	.then(todos => console.log(todos))


2. POST 요청

request.post("https://jsonplaceholder.typicode.com/todos", {
	userId: 1,
    title: "JavaScript",
	completed: false
}).then(response => response.json())
	.then(todos => console.log(todos))
	.catch(err => console.error(err))



3. PATCH 요청

 

request.patch("https://jsonplaceholder.typicode.com/todos/1", {
	completed: true
}).then(response => response.json())
	.then(todos => console.log(todos))
    .catch(err => console.error(err));

4. DELETE 요청

 

request.delete("https://jsonplaceholder.typicode.com/todos/1")
	.then(response => response.json())
	.then(todos => console.log(todos))
	.catch(err => console.error(err));

'js' 카테고리의 다른 글

제너레이터  (1) 2023.04.11
에러처리  (0) 2023.04.09
43 ajax  (0) 2023.03.28
42장 비동기 처리  (0) 2023.03.27
41장 타이머  (2) 2023.03.26