⚡자바스크립트의 프로미스
프로미스는 자바스크립트 비동기 처리에 사용되는 객체입니다.
객체이기 때문에 생성자 함수를 호출하여 인스턴스화 할 수 있습니다.
자바스크립트는 특정 코드의 실행이 완료될때까지 기다리지 않고
실행할 수 있는 다음 코드를 먼저 수행하는 특성을 갖고 있기때문에 생기는 문제들이 있습니다.
프로미스는 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로
비동기 연산이 종료된 이후 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있읍니다.
그래서 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있어요
자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 전통적인 콜백 패턴은 콜백 헬로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리가 곤란하며 여러 개의 비동기 처리를 한번에 처리하는 데도 한계가 있다.
ES6에서는 비동기 처리를 위한 또 다른 패턴으로 프로미스(Promise)를 도입했다. 프로미스는 전통적인 콜백 패턴이 가진 단점을 보완하며 비동기 처리 시점을 명확하게 표현할 수 있다는 장점이 있다.
https://poiemaweb.com/es6-promise
⚡동기식 처리 모델 ? 비동기식 처리 모델?
동기식 처리 모델은 직렬적으로 태스크를 수행합니다. 태스크는 순차적으로 실행되고
어떤 작업이 수행 중이라면 다음 태스크는 대기합니다.
비동기식 처리 모델은 병렬적으로 태스크를 수행합니다.
태스크가 종료되지 않은 상태라 하더라도 대기하지 않고 즉시 다음 태스크를 실행합니다.
예를 들어 서버에서 데이터를 가져와서 화면에 표시하는 태스크를 수행할 때
서버에 데이터를 요청한 다음 서버의 데이터 응답을 기다리지않고(non-blocking)
즉시 다음 태스크를 수행합니다.
자바스크립트에서 자주 사용되는 비동기식 처리 모델은 요청을 병렬로 처리해서
다른 요청이 블로킹 되지 않는다는 장점이 있지만
비동기 처리를 위해서 콜백 패턴을 사용하면 콜백의 처리 순서를 보장해주기 위해
여러개의 콜백함수가 중첩되어 복잡도가 높아지는데 이것을 콜백헬이라고 합니다.
⚡콜백 지옥
<!DOCTYPE html>
<html>
<body>
<script>
// 비동기 함수
function get(url) {
// XMLHttpRequest 객체 생성
const xhr = new XMLHttpRequest();
// 서버 응답 시 호출될 이벤트 핸들러
xhr.onreadystatechange = function () {
// 서버 응답 완료가 아니면 무시
if (xhr.readyState !== XMLHttpRequest.DONE) return;
if (xhr.status === 200) { // 정상 응답
console.log(xhr.response);
// 비동기 함수의 결과에 대한 처리는 반환할 수 없다.
return xhr.response; // ①
} else { // 비정상 응답
console.log('Error: ' + xhr.status);
}
};
// 비동기 방식으로 Request 오픈
xhr.open('GET', url);
// Request 전송
xhr.send();
}
// 비동기 함수 내의 readystatechange 이벤트 핸들러에서 처리 결과를 반환(①)하면 순서가 보장되지 않는다.
const res = get('http://jsonplaceholder.typicode.com/posts/1');
console.log(res); // ② undefined
</script>
</body>
</html>
비동기 함수 내의 readystatechange 이벤트 핸들러에서 처리 결과를 반환(①)하면 순서가 보장되지 않는다. 즉, ②에서 get 함수가 반환한 값을 참조할 수 없다. 그 이유에 대해 살펴보자.
get 함수가 호출되면 get 함수의 실행 컨텍스트가 생성되고 호출 스택(실행 컨텍스트 스택)에서 실행된다. get 함수가 반환하는 xhr.response는 readystatechange 이벤트 핸들러가 반환한다. readystatechange 이벤트는 발생하는 시점을 명확히 알 수 없지만 반드시 get 함수가 종료한 이후 발생한다. get 함수의 마지막 문인 xhr.send();가 실행되어야 request를 전송하고 request를 전송해야 readystatechange 이벤트가 발생할 수 있기 때문이다.
get 함수가 종료하면 곧바로 console.log(②)가 호출되어 호출 스택에 들어가 실행된다. console.log가 호출되기 직전에 readystatechange 이벤트가 이미 발생했다하더라도 이벤트 핸들러는 console.log보다 먼저 실행되지 않는다.
readystatechange 이벤트의 이벤트 핸들러는 이벤트가 발생하면 즉시 실행되는 것이 아니다. 이벤트가 발생하면 일단 태스크 큐로 들어가고 호출 스택이 비면 그때 이벤트 루프에 의해 호출 스택으로 들어가 실행된다. console.log 호출 시점 이전에 readystatechange 이벤트가 이미 발생했다하더라도 get 함수가 종료하면 곧바로 console.log가 호출되어 호출 스택에 들어가기 때문에 readystatechange 이벤트의 이벤트 핸들러는 console.log가 종료되어 호출 스택에서 빠진 이후 실행된다. 만약 get 함수 이후에 console.log가 100번 호출된다면 readystatechange 이벤트의 이벤트 핸들러는 모든 console.log가 종료한 이후에나 실행된다.
때문에 get 함수의 반환 결과를 가지고 후속 처리를 할 수 없다. 즉, 비동기 함수의 처리 결과를 반환하는 경우, 순서가 보장되지 않기 때문에 그 반환 결과를 가지고 후속 처리를 할 수 없다. 즉, 비동기 함수의 처리 결과에 대한 처리는 비동기 함수의 콜백 함수 내에서 처리해야 한다. 이로 인해 콜백 헬이 발생한다.
만일 비동기 함수의 처리 결과를 가지고 다른 비동기 함수를 호출해야 하는 경우, 함수의 호출이 중첩(nesting)이 되어 복잡도가 높아지는 현상이 발생하는데 이를 Callback Hell이라 한다.
Callback Hell은 코드의 가독성을 나쁘게 하고 복잡도를 증가시켜 실수를 유발하는 원인이 되며 에러 처리가 곤란하다.
출처 : https://poiemaweb.com/es6-promise
자바스크립트 딥다이브에서 가져온 예제입니다.
1. 실행컨텍스트 스택에 get이 들어감
https://www.youtube.com/watch?v=8aGhZQkoFbQ
좀 더 딥한 부분을 알고 싶으시다면
위 영상을 추천드립니다.
전 글 다 쓰고 보려구요..
아무튼 요약하자면
비동기 처리를 위해 콜백 패턴을 사용하면
처리 순서를 보장하기 위해서 콜백의 안에서만 처리를 해야하고
그렇게되면
step1(function(value1) {
step2(value1, function(value2) {
step3(value2, function(value3) {
step4(value3, function(value4) {
step5(value4, function(value5) {
// value5를 사용하는 처리
});
});
});
});
});
이렇게 지옥의 들여쓰기를 해야한다.
또한 이런식으로 콜백 방식의 비동기 처리를 하게되면 에러처리가 곤란해진다는 문제가 있읍니다
이러한 문제를 극복하기 위해 Promise가 제안되었습니다.
🔍프로미스의 생성
프로미스는 new Promise()를 이용해 생성가능합니다.
promise 생성자 함수는 비동기 작업을 수행할 콜백함수를 인자로 전달 받는데
이 콜백함수는 resolve와 reject 함수를 인자로 전달받습니다.
const promise = new Promise((resolve, reject) => {
console.log('doing something...')
setTimeout(() => {
resolve(console.log("sucess"));
reject(new Error('no network'))
}, 2000);
})
promise
.then(value => {
console.log(value)
})
.catch(error => {
console.log(error)
})
.finally(final => {
console.log("final")
})
promise의 후속처리 메소드로는 then, catch, finally 가 있습니다.
then | then메소드는 두개의 콜백함수를 인자로 전달 받습니다. 첫번째 콜백 함수는 성공 시 호출되고 두번째 함수는 실패시 호출됩니다. then 메소드는 Promise를 반환합니다. |
catch | 예외(비동기 처리에서 발생한 에러와 then 메소드에서 발생한 에러)가 발생하면 호출됩니다. catch 는 promise를 반환합니다. |
finally | 성공 실패 여부와 상관없이 promise가 확정 될 때 호출됩니다. then,catch핸들러 모두에서 사용되야할 코드를 사용하기 좋은 메서드입니다. |
const fetchNumber = new Promise((resolve,reject) => {
setTimeout(() => resolve(1) , 1000);
})
fetchNumber
.then(num => num*2)
.then(num => num*3)
.then(num => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(num - 1)
}, 1000);
})
})
.then(num => console.log(num))
다음 예제입니다.
위 코드의 결과는 어떻게 될까요?
const fetchNumber = new Promise((resolve,reject) => {
setTimeout(() => resolve(1) , 1000);
})
fetchNumber
.then(num => num*2) // 1 * 2 = 2
.then(num => num*3) // 2 * 3 = 6
.then(num => {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve(num - 1) // 6 -1
}, 1000);
})
})
.then(num => console.log(num)) // console.log(5)
답은 5입니다.
then은 promise가 성공 시 호출되고 체이닝되어있으니까
체이닝 된 순서대로 코드가 수행될테니까요
조금 더 예제를 꼬아볼까요?
const getHen = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('암탉') , 1000)
})
const getEgg = hen =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${hen} => 계란`) , 1000)
})
const cook = egg =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${egg} => 냠냠`) , 1000)
})
getHen()
.then(hen => getEgg(hen))
.catch(error => {
return '에러빵'
})
.then(cook)
.then(meal => console.log(meal))
위 예제에서 getHen에 즉시실행함수를 할당하는데
즉시실행 함수의 내용은 promise에 대한 내용이네요
1초뒤에 resolve로 암탉이라는 문자열을 뱉습니다.
getEgg는 매개변수 하나를 받는 함수로 '매개변수 => 계란' 을 리턴합니다.
그 아래에 cook은 '매개변수 => 냠냠'을 리턴하네요
아래 코드의 출력 결과는 어떻게 될까요?
암탉
암탉 => 계란
암탉 => 계란 => 냠냠
console.log('암탉 => 계란 => 냠냠')
순으로 수행되어서 이런 결과가 나오는군용
콜백패턴과 비교해서 들여쓰기도 줄어들고 가독성도 좋아졌는데
처리순서가 보장되는 결과를 얻을 수가 있네요
'javascript' 카테고리의 다른 글
자바스크립트 실행 컨텍스트 (execution context) 란? (2) (1) | 2022.12.25 |
---|---|
자바스크립트 실행컨텍스트 (execution context) 란? (1) | 2022.12.25 |
유사 배열이 대체 뭐임? Immutability는 뭐임? (0) | 2022.12.20 |
널리쉬 병합 연산자 '??' (Nullish coalescing operator) Javascript (1) | 2022.12.15 |
자바스크립트 JS Map()자료구조 정리! (1) | 2022.11.15 |