-
자동차경주 예제로 Promise 객체의 개념 이해해보기Javascript 2022. 1. 29. 20:18
Q.
아반떼, 소나타, 제네시스가 자동차경주를 한다.
각 차량은 비동기인 setTimeout을 주행시간으로 가지는 함수이다.
각 차량의 주행시간은 매번 랜덤하게 정해진다.
이때,
(1) 세 차가 동시에 출발한 경우, 마지막 차량이 들어온 뒤 "경주끝" 이라는 문자를 출력하도록 해보자.
(2) 차량의 순서를 정해 한 차가 들어온 뒤 다음 차가 출발하도록 만들어 보자.
const time = () => { return Math.ceil(Math.random() * 10) * 1000 } //time()은 1000~10000 사이의 값을 1000 단위로 랜덤하게 출력 const 아반떼 = () => { console.log('아반떼 go') setTimeout(() => { console.log('아반떼 end') }, time()) } const 소나타 = () => { console.log('소나타 go') setTimeout(() => { console.log('소나타 end') }, time()) } const 제네시스 = () => { console.log('제네시스 go') setTimeout(() => { console.log('제네시스 end') }, time()) }
1. 일단 세 차량을 (거의) 동시에 출발시켜보자
아반떼() 소나타() 제네시스()
단순히 각 차량의 함수를 실행시켜 결과를 보았다.
go 가 출력된 뒤, setTimeout에서 부여된 각각의 time()함수 값에 따라 end가 출력된다.
2. 모든 차가 들어온 뒤, "경기끝"이라는 문자를 출력시켜보자
차가 들어오는 순서는 알 수 없지만 마지막 차가 들어온 뒤에만 "경기끝"을 출력하고 싶다. 어떻게 하면 될까?
2-1. 단순하게 차량의 함수의 뒤에 console.log("경기끝")을 추가해보았다
아반떼() 소나타() 제네시스() console.log("경기끝!")
당연하게도 각 함수의 go가 출력된 뒤 경기끝 이라는 문자가 바로 출력되고, 그 이후에 비동기식인 setTimeout이 실행되어 end가 출력된다.
비동기식함수는 걸리는 시간과는 상관없이 무조건 동기식 처리가 다 끝난 뒤에 실행이 된다. (시간이 0이더라도)
👉 즉, "경기끝"이라는 문자는 반드시 비동기식 함수의 내부에, end를 출력하는 코드의 다음에 코드를 작성하여야 한다.
그렇다면 어떻게 코드를 짜야 마지막 차량의 뒤에만 "경기끝"을 출력할 수 있을까?
2-2. 콜백함수를 이용해 비동기식으로 출력해보자.
먼저, 문자를 출력하는 함수 하나를 짜보자.
let count = 0 //finish 함수 const finish = () => { count += 1 if (count === 3) { console.log('경기끝') } }
차가 하나씩 들어올 때마다 count에 +1을 하고 총 세대의 차가 모두 들어온 경우에만 "경기끝"을 출력하는 finish()라는 함수를 만들었다.
콜백함수를 받을 수 있도록 차량의 함수를 다시 구성했다.
const 아반떼 = cb => { //인자로 cb함수를 넣어준다 console.log('아반떼 go') setTimeout(() => { console.log('아반떼 end') cb() //setTimeout의 내부에서 cb를 실행시킨다 }, time()) } const 소나타 = cb => { console.log('소나타 go') setTimeout(() => { console.log('소나타 end') cb() }, time()) } const 제네시스 = cb => { console.log('제네시스 go') setTimeout(() => { console.log('제네시스 end') cb() }, time()) }
이제 차량함수의 인자로 들어온 함수는 무조건 end가 출력된 뒤에 실행이 될 것이다.
아반떼(finish) 소나타(finish) 제네시스(finish)
이렇게 finish함수를 인자로 넣어 실행시켜보았다.
❗️❗️아반떼(finish()) 이런식으로 함수를 실행시킨 채 인자로 넣는 실수를 하지않도록 주의하자
원하던 결과가 잘 출력된다!
결론 👉 비동기식으로 실행되는 A함수 뒤에 B함수를 실행시키고 싶다면, 함수를 인자로 받아 비동기 내부에서 실행되도록 A함수를 구성하면 된다.
그리고 A함수의 인자에 B함수를 넣어준다
3. 세 차량의 순서를 정해 이어달리기를 하도록 만들어보자.
소나타 - 제네시스 - 아반떼의 순서로 함수가 실행되게 하고 싶다. 어떻게 하면 될까?
위에서 사용한 방법을 그대로 사용하면 된다!
소나타의 뒤에 제네시스를 실행시키고 싶다면 소나타의 인자로 제네시스를 넣고,
제네시스의 뒤에 아반떼를 실행시키고 싶다면 제네시스의 인자로 소나타를 넣으면 된다.
//1 소나타(제네시스(아반떼))
이런 구성이다.
그런데 여기서 주의해야 할 점이 하나 있다.
각각의 소나타, 제네시스, 아반떼는 모두 콜백함수를 인자로 가지는 함수이다.
이런 경우에는 각 함수를 인자로 넣을 때 익명 함수를 사용해야한다.
//2 소나타(() => { // 익명함수를 인자로 넣어주세요! 제네시스(() => { 아반떼(() => { }) }) })
왜냐하면 직접적으로 함수를 인자로 넣은 경우에는 해당 함수 자체가 아닌 함수의 리턴값이 인자로 들어가버리기 때문이다.
위에서 짜놓은 차량의 함수를 다시 읽어보면
함수가 아니라 리턴값이 인자로 들어간 경우 setTimeout내부에서는 리턴값() 이라는 코드를 실행하게 된다.
리턴값이 없으니 없는 함수를 찾아 ()로 실행을 해야하는 상황이다. cb()를 찾지 못한다는 에러를 보게된다.
또한 내부의 값을 먼저 찾아 실행을 하려고 하기때문에 내부의 함수가 먼저 실행이 되어버린다.
❗️2-2에서 아반떼(finish)가 아니라 아반떼(finish())를 입력한 것과 마찬가지라고 볼 수 있다.
4. 그렇다면 차량이 늘어난다면 어떻게 될까?
각 차량을 3대씩, 총 9대의 차량이 이어달리기를 해야하는 상황이다.
코드는 위와 동일한 구조로 짜면 된다.
아반떼(() => { 소나타(() => { 제네시스(() => { 아반떼(() => { 소나타(() => { 제네시스(() => { 아반떼(() => { 소나타(() => { 제네시스(() => { //콜백지옥🔥 }) }) }) }) }) }) }) }) })
....ㅎㅎ....
이런 형태를 콜백지옥이라고 한다.
그런데 앞으로 짜게될 코드에서 이런식으로 많은 함수들을 순서대로 실행시키고 싶을 때가 많을텐데 매번 이런 콜백지옥을 만들어야할까?
아니다!
그래서 등장한 것이 Promise 객체이다.
5. Promise 객체란?
우리는 비동기함수 setTimeout의 내부에 콜백함수를 넣어 실행시키는 방식으로 순서를 부여했다.
프로미스 객체는 비동기함수의 결과를 보장해주는 용도로 사용한다.
Promise객체는 말그대로 비동기함수가 실행되면 그 결과를 전해주겠다는 "약속"이다.
비동기함수가 실행되기 전까지는 pending상태로 존재하다가
비동기함수가 실행된 뒤에는 약속대로 값을 전달해준다.
코드로 짜자면 다음과 같은 구조이다.
let pr = new Promise((resolve, reject) => { resolve('OK') reject('NO') })
사실 여기는 내가 아직 잘 모르는 부분이기는 한데, 비동기함수는 제대로 결과값을 출력하며 성공할 때도 있지만 어쩔때는 실패를 하기도 한다. (지금은 실패를 한다는 개념을 잘 모르겠다) 아무튼 성공을 하면 resolve에 담긴 값(OK)을 전달해주고, 실패를 하면 reject에 담긴 값(NO)을 전달해준다.
위에서 짠 차량함수를 프로미스객체로 바꿔보면 다음과 같다.
const 아반떼 = new Promise((resolve, reject) => { console.log('아반떼 go') setTimeout(() => { resolve('아반떼 end') }, 1000) }) const 소나타 = new Promise((resolve, reject) => { console.log('소나타 go') setTimeout(() => { resolve('소나타 end') }, 1000) }) const 제네시스 = new Promise((resolve, reject) => { console.log('제네시스 go') setTimeout(() => { resolve('제네시스 end') }, 1000) })
위 차량 객체들을 순서대로 실행시키고 싶다면 다음과 같이 then을 사용하여 비동기함수의 결과값을 받아와 사용한다.
소나타 //1 .then(data => //2 console.log(data) //3 return 제네시스 //4 }) .then(data => { //5 console.log(data) //6 return 아반떼 //7 }) .then(data => { //8 console.log(data) //9 })
말로 풀어서 설명을 한다면
1. 소나타가 실행되고 go출력 - setTimeout성공 - resolve값(end)을 반환한다.
2. then
3. resolve값을 인자(data)로 받아와 console.log(data)로 end를 출력한다
4. return으로 다음 차량인 제네시스가 실행된다. go출력 - setTimeout성공 - resolve값(end)을 반환한다.
5. then
6. resolve값을 인자(data)로 받아와 console.log(data)로 end를 출력한다
7. return으로 다음 차량인 아반떼가 실행된다. go출력 - setTimeout성공 - resolve값(end)을 반환한다.
8. then
9. resolve값을 인자(data)로 받아와 console.log(data)로 end를 출력한다
이렇게 비동기함수의 순서를 then을 사용해 보기 쉽게 정리할 수 있다!
then말고 async/await을 사용한 방법도 있다. 그것도 천천히 정리해놓을 예정이다..
-
사실 이거 쓰다가 뒤로가기 잘못 눌러서 한번 날려먹고 (다써서 등록만 누르면 되는 상태였음ㅎ..)
멘탈 깨져서 다시 쓸까말까 고민했는데 정리 안하고 넘어가면 나중에 헷갈릴거 같아서 다시 썼다
혹시나 비공개로 미리 등록해두고 수정으로 내용 작성하고 있다면... 꼭 중간중간에 글 등록해서 저장해두길
수정에는 자동저장기능도 임시저장버튼도 없다
그냥
싹다 날라간다
🥲
'Javascript' 카테고리의 다른 글
6. To do list 만들기 (0) 2022.01.12 5. javascript - 객체의 복사 (0) 2022.01.10 4. javascript - html DOM 조작 (0) 2022.01.10 3. javascript 기초 - forEach, 배열중복제거 (0) 2022.01.06 2. javascript 기초 - array와 object (0) 2022.01.04