JS 스터디 『Promise』
서울에서 부산으로 가는 2가지 상황을 생각해보자.
A: (기차를 타고) 서울에서 부산까지 가는 도중에 책1권 봤어 B: (운전하면서) 서울에서 부산까지 가는 도중에 책1권 봤어. |
A의 말은 신빙성이 있다. 하지만 B의 말은 언뜻 이해하기 쉽지 않다.
아마도 B는 '휴게소'에서 책을 읽었거나 '출발 or 도착'한 뒤, 차안에서 책을 읽었을 것이다.
이렇게 추론할 수 있는 이유는,
A는 외부에 일(기차)을 시켰고, B는 본인이 직접 일(운전)을 했기 때문이다.
│동기 vs 비동기
억지로 갖다 붙이자면, A와 같은 방식(기차타고 이동)은 '비동기식'이며,
B와 같이 순서대로 하나씩 처리하는 방식(자가운전)은 '동기식'이다.
쉽게 말해, 비동기식은 '외주'에 일을 맡기는 방식이며,
동기식은 회사가 자체적으로 모든 일을 처리하는 방식이다.
현실에서도 '비동기', '동기'방식의 비교가 가능한데,
제품을 직접적으로 생산하지 않는 '에어비앤비, 우버, 나이키'와 같은 비동기식과
직접적으로 제품을 생산 or 매입하는 전통적인 회사(동기식)들을 꼽을 수 있다.
비동기식의 장점은 '순서'에 구속받지 않는다는 점이다.
에어비앤비는 판매자와 구매자를 동시에 유치할 수 있다.
반면, 힐튼 호텔은 호텔이 완성되지도 않았는데 구매자를 동시에 받을 수는 없다.
따라서 비동기식이 확장면에서 간편하고, 속도가 빠를 수 있다.
│비동기의 단점
비동기식 방법이 상승세를 유지하고 있지만
이 역시 단점이 존재한다. 가령, 아래 상황을 가정해보자.
A는 서울에서 부산까지 기차를 타고 내려가야한다. 그리고 '수원', '대전', '김천', '대구', '울산' 각각 기차역에 내려서 친구들에게 물건을 받아야 한다. |
A는 친구들에게 자신이 기차역에 몇시까지 도착할 것이라는 연락(정보전달)을 해야한다.
그런데 대전역에서 기차가 연착되는 바람에 시간이 늦어졌고,
김천역에서 만나기로 했던 친구에게 '다음에 보자'는 문자를 보냈다.(약속::Promise구문)
그러나 A의 문자가 전송되기 전에 김천에 있는 친구가 벌써 김천역에 도착해버렸다.
결국 김천역에 있는 친구에게 선물을 받지 못한채 빈손으로 돌아간다.(오류발생)
만일 A가 직접 차를 운전했다면, 다소 시간이 늦어지더라도 친구들을 한명씩 만날 수 있었을 것이다.
(동기식 프로그래밍)
결국, 비동기식의 단점은 '실행시점'에 있다.
비동기식 자체가 외부에 기능을 맡겨놓은 방식이므로(외주제작)
약속한 시간에 약속된 결과물이 반환되어야만 한다.
만일, 유저의 데이터를 받아서 동작하는 함수가
유저의 데이터를 받기도 전에 먼저 동작해버리면 오류가 발생한다.
이는, 문자를 보지않고 빨리 김천역에 도착한 친구와 비슷한 상황이다.
그래서 특정한 시간대에 동작해라는 일종의 '약속', Promise구문이 필요하다.
│Promise
특정 시점에 특정기능을 실행하기 위해서는 Promise를 실행해야 한다.
서울~부산까지 내려갈 때, '김천'약속을 가장 마지막으로 연기하는 코드는 아래와 같다.
let arr1 = ['수원','대전','대구','울산'];
let arr2 = '김천'
getProduct1(arr1);
getProduct2(arr2);
function getProduct1(arr){
//서버에서 파일 받아온다고 가정
setTimeout(()=>{
for(let item of arr){
console.log(`${item}에서 물건을 받았습니다.`)
}
},1000)
}
function getProduct2(arr){
console.log(`${arr}에서 물건을 받았습니다.`)
}
동기식 프로그램이라면, getProduct1()함수가 실행된 후 getProduct2()함수가 실행된다.
하지만 자바스크립트에서 위의 코드를 실행해보면 아래와 같은 결과가 나온다.
마지막에 '김천'에서 물건을 받아야하는 상황이지만
자바스크립트는 순서대로 처리하지 않고 '비동기식'으로 실행한다.
그 결과, getProduct2()가 먼저 실행된다.
('김천'에서 물건을 받았습니다.)
만일, 김천을 가장 늦게 실행하려면 아래와 같이 Promise를 사용할 수 있다.
let arr1 = ['수원','대전','대구','울산'];
let arr2 = '김천'
getProduct(arr1,arr2)
.then((res)=>{
getProduct2(res);
});
function getProduct(arr1,arr2){
//서버에서 파일 받아온다고 가정
return new Promise((resolve,reject)=>{
for(let item of arr1){
console.log(`${item}에서 물건을 받았습니다.`);
}
resolve(arr2);
});
}
function getProduct2(arr){
console.log(`${arr}에서 물건을 받았습니다.`)
}
new Promise구문을 return문으로 처리했을 때,
resolve(val) 의 'val'값이 .then()메서드의 결과값으로 넘어간다.
위에서는 '김천'을 가장 늦게 실행하려는 의도이므로
.then()구문에서 getProduct2()를 실행하면 동기식으로 처리할 수 있다.
│Promise.all
Promise.all구문은 여러 비동기 처리를 병렬로 실행하고,
결과값(성공한 경우)을 배열로 전달한다.
위의 코드를 Promise.all로 처리하면 아래와 같다.
Promise.all([
getProduct('수원'),
getProduct('대전'),
getProduct('대구'),
getProduct('울산'),
getProduct('김천')
]).then(res=>{
for(let item of res){
console.log(item);
}
})
function getProduct(val){
//서버에서 파일 받아온다고 가정
return new Promise((resolve,reject)=>{
resolve(`${val}에서 물건을 받았습니다.`);
})
}
Promise.all()메서드는 배열(인수)에 전달된 모든 Promise객체가 resolve()한 경우에만
then메서드의 성공 콜벡을 실행한다. 이때 then()메서드에 전달되는 반환값은 '배열'이라는 점에 유의한다.
Promise.all에 전달된 배열 중에서 단 하나라도 실패한다면,
reject()가 실행되다는 점도 알아두자.