2023.10.14 - [🌳Frontend/etc] - Promise Async/Await
이전에 Promise Async/Await 에 대해서 정리한 게시글이 있지만,
Promise에 대해 예제코드와 함께 좀 더 톺아보려고 해요.
🤔 Promise가 없다면?
Promise에 대해 알아보기 이 전에, Promise를 사용하기 전의 상황을 살펴봅시다.
우선 자바스크립트는 싱글스레드 언어로 동기적으로 코드를 실행한다는 것은 알고있을거에요.
싱글스레드란 한번에 하나의 일만 실행하는 것을 의미해요.
상황을 가정해봅시다.
직장인 ㅁㅁ씨는 업무를 하고 점심을 먹으러 갈거에요.
그 업무를 다 처리하기 위해선 5초정도 걸린다고 생각해봅시다. (ㅁㅁ씨가 사는 세계는 5초가 5시간이라고 생각해봐요.)
export const goLunch = () => {
console.log('우리 식사하러 갈까요? 🍚')
}
export const working = () => {
// 5초 이상 걸리는 작업
for (let i = 0; i < 5000000000; i++) {}
console.log('작업 완료 ! 💻')
}
working()
goLunch()
ㅁㅁ씨는 업무를 하고(`working()`) 점심을 먹으러 갈거에요. (`goLunch()` )
실제로 위의 코드를 실행해보면, 정상적으로 콘솔에 노출이 됩니다.
지금이야 `working()` 함수가 5초밖에 안걸리니... 상관은 없지만..
working()
working()
working()
working()
working()
goLunch()
위 처럼, 5초이상 걸리는 업무를 여러번 하게되면 어떻게 될까요?
그러면 5초보다 훨신 더 오래걸리겠죠.
자바스크립트는 싱글스레드 언어로, 한번에 하나의 작업을 한다는 건 기억하실거에요.
그렇다면, 자바스크립트는 `working()` 함수를 정직하게 1분동안 실행할거에요.
그 동안, 웹사이트를 이용하는 사용자는 `working()` 이 실행되는 동안, 어떠한 액션을 취할 수 없어요.
왜냐하면, 자바스크립트는 싱글스레드 언어니까.
다시 예시를 들어봅시다.
게시판 목록을 불러오는 API가 1분 이상 걸린다고 생각해봅시다.
그렇다면 사용자는 게시판을 불러올 때 까지 빈 화면을 보고있을거에요. 끔찍합니다. 💬
이 상황을 어떻게 처리하면 좋을까요?
💡 비동기로 처리하면 되네요.
해결방법은 간단합니다.
오래걸리는 업무는 비동기로 처리하면 되는거에요.
비동기는 여러 일을 동시에 처리하도록 도와줍니다.
`working()` 은 실행시키고, 그 사이에 다른 업무를 처리하면 되는거아닌가요?
다시 상황을 가정해봅시다.
ㅁㅁ씨는 자신의 업무를 대신해주는 로봇을 개발했어요.
그 로봇에게 일을 넘기고 점심을 먹으러갈까해요.
export const goCommand = () => {
console.log('로봇이 대신 처리하도록 ! 🤖')
}
export const goLunch = () => {
console.log('우리 식사하러 갈까요? 🍚')
}
export const working = () => {
// 5초 이상 걸리는 작업
setTimeout(() => {
console.log('작업 완료 ! 🤖')
}, 5000)
}
goCommand()
working()
goLunch()
유명한 비동기처리 함수인 setTimeout을 사용했어요.
5초뒤에 로봇은 업무를 완료시킬거에요.
만약, 비동기처리함수를 사용하지 않았다면, 어땠을까요?
업무를 처리하느라 점심은 커녕 저녁도 못먹었을거에요.
💬 그래서 Promise가 왜 필요한데요?
우리 콜백지옥이라는 용어를 한번쯤 들어봤을거에요.
위에는 업무를 working() 하나로 예시를 들었지만, 업무의 종류가 매우매우 많다고 생각해봐요.
그리고 그 업무는, 앞서 선행되었던 업무가 끝나야 진행 할 수 있다고 가정해봐요.
export const working = () => {
setTimeout(() => {
console.log('첫번째 작업 완료 ! 🤖')
working2()
}, 5000)
}
export const working2 = () => {
setTimeout(() => {
console.log('두번째 작업 완료 ! 🤖')
working3()
}, 5000)
}
export const working3 = () => {
setTimeout(() => {
console.log('세번째 작업 완료 ! 🤖')
wokring4()
}, 5000)
}
....
업무가 끝나면 또 업무를 실행하는 함수를 호출하고, 그 함수가 끝나면 또 호출하고.....
위의 코드는 함수로 정리해서 깔끔해보이지만, 풀어쓰면 아래와 같아요.
const fullCallbackWorking = () => {
setTimeout(() => {
console.log('첫번째 작업 완료 ! 🤖')
setTimeout(() => {
console.log('두번째 작업 완료 ! 🤖')
setTimeout(() => {
console.log('세번째 작업 완료 ! 🤖')
setTimeout(() => {
console.log('네번째 작업 완료 ! 🤖')
setTimeout(() => {
console.log('다섯번째 작업 완료 ! 🤖')
}, 5000)
}, 5000)
}, 5000)
}, 5000)
}, 5000)
}
보기 너무 힘들지 않나요?
콜백함수를 분리하면 깔끔해지기야 하겠지만, 한계가 있을거에요.
무엇보다 코드는 가독성이 좋아야하는데, 이건.. 너무 심해요.
게다가, 만약 코드에 에러가 생기면 에러처리를 해주어야 하는데요,
export const working = () => {
try {
setTimeout(() => {
console.log('첫번째 작업 완료 ! 🤖')
working2()
}, 5000)
} catch (e) {
console.log('으악! 😵')
}
}
`try-catch` 문을 사용해서 처리가 할 수가 있어요.
이러한 처리를 위의 예제코드에 추가를 해야한다면........ 흠. 해보진 않았지만 끔찍하네요.
이러한 문제를 해결하기 위해 여러 비동기처리 패턴은 많았지만, 지금 표준으로 잡힌 패턴은 promise 패턴이에요.
정리
우리는 업무를 동시에 처리하기 위해 비동기 처리를 합니다.
비동기 처리하는 패턴에는 여러가지가 있었지만, 표준이 정해졌는데, 그것이 promise 패턴입니다.
길고 길었네요.
Promise를 new연산자와 함께 호출하면 Promise 객체를 생성해요.
Promise 생성자 함수는 비동기 처리를 수행할 콜백함수를 인수로 전달받아요.
// 프로미스 생성
const promise = new Promise((resolve, reject) => {
// Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행
if (/* 비동기 처리 성공 */) {
resolve('result');
}
else { /* 비동기 처리 실패 */
reject('failure reason');
}
});
이게 갑자기 무슨소리죠?
아까 예제로 비교해봅시다.
export const workingWithPromise = () => {
return new Promise((resolve, reject) => {
// ** Promise 객체 내부에서 비동기 처리를 수행! **
setTimeout(() => {
// 인수로 받은 resolve 로 성공
resolve('첫번째 작업 완료 ! 🤖')
}, 5000)
})
.then((value) => {
// resolve 호출시 실행
console.log(value)
})
.catch((e) => {
// reject 호출시 실행
console.log('으악! 😵')
})
}
Promise의 상태
Promise는 비동기 처리가 어떻게 진행되고 있는지 상태 정보를 가지고 있어요.
- pending: 아직 수행 전 이에요.
- fulfilled: 수행했는데 성공했어요.
- rejected: 수행했는데 실패했어요.
이때, 비동기 처리가 성공하면, resolve 함수를 호출하고, fulfilled 상태가 되고,
실패하면 reject 함수를 호출하고 rejected 상태가 되는거에요.
여기서 중요한건, Promise의 상태가 한번 변하면 절대로 다른 상태로 변할 수가 없어요.
예를들면, 한번 rejected 상태를 가지게 되었다면, fullfilled 가 될 수 없어요.
그리고 Promise는 후속처리 메서드도 제공합니다.
`then()` 과 `catch()` 인데요,
비동기처리가 성공하고 나면(fullfilled) Promise의 비동기처리 결과를 가지고 무언가를 해야 할 때도 있고,
실패하더라도, 에러 처리가 필요하겠죠?
새로운 예제로 감을 잡아봅시다.
export const 버스를_타러_갑시다 = (hour: number, second: string) => {
return new Promise((resolve, reject) => {
if (hour <= 7) {
resolve('무사 출근 완료! 😎')
} else {
reject(second)
}
})
}
7시 이전에 버스를 타면 무사히 출근할 수 있고, 그 이후 시간대는 실패하고 두번째 방법으로 출근을 해야해요.
const 민수 = 버스를_타러_갑시다(7, '지하철')
const 철수 = 버스를_타러_갑시다(8, '지하철')
민수.then((result) => console.log(`민수 ` + result)).catch((reason) => {
console.log(`민수 최후의 수단으로 ${reason}를 타러 가고 있어요.`)
})
철수.then((result) => console.log('철수 ' + result)).catch((reason) => {
console.log(`철수 최후의 수단으로 ${reason}를 타러 가고 있어요. 😅`)
})
민수는 출근을 성공했지만, 철수는 실패했네요.
이처럼, Promise의 비동기 처리의 상태가 변하면, 후속 처리 메서드(then, catch) 로 처리할 수 있어요.
'🌳Frontend > etc' 카테고리의 다른 글
[Node.js] dotenv 로 .env 파일에 환경변수로 설정시 undefined로 읽는 오류 (0) | 2023.12.20 |
---|---|
[careerly] 주니어 프론트 개발자인데 매일 자괴감을 느낍니다. (0) | 2023.10.28 |
Next.js의 Cypress에서 테스트코드 실행해보기 (feat. MSW) (1) | 2023.10.21 |
Next.js환경에서 Cypress시작하기 (Cypress 설치/실행) (0) | 2023.10.21 |
Promise Async/Await (1) | 2023.10.14 |