[Nodejs] 비동기 처리, Promise
JavaScript의 비동기 처리에 대해서
✏️ JavaScript의 특징
- JavaScript는 싱글 스레드 언어이다.
- 즉, JavaScript는 기본적으로 Synchronous 하다.
JS의 기본은 “동기”이다.
그렇다면, Ajax 요청, 이벤트 리스너,
SetTimeout등 처리하는데 시간이 오래 걸리는 작업들을 계속 기다려야 하는가?
위와 같은 작업들은 자바스크립트 엔진이 아닌, 브라우저 내부의 멀티 스레드인 Web APIs에서 비동기 + 논블로킹으로 처리된다. ➜ 비동기의 핵심 요소는 JS가 아니라 브라우저이다.
⚠️ Node.js에서는 libuv 내장 라이브러리가 처리한다.
✏️ 이벤트 루프
이벤트 루프
: 브라우저의 동작 타이밍을 제어하는 관리자 이벤트 루프를 통해서 JS 코드를 멀티스레드처럼 동작하게 만들 수 있다.
■ 구성 요소
- Call Stack: JS 엔진이 코드 실행을 위해 사용하는 메모리 구조
- Web APIs: 브라우저에서 제공하는 API 모음, 비동기 처리 담당
- Callback Queue: 비동기 작업이 완료되면 실행되는 함수들이 대기하는 공간
■ 특징
- JS 코드가 실행되면, Call Stack에 함수가 쌓인다.
- Call Stack 이 동기적으로 작업을 처리하다가, 비동기 작업을 만나면 Web APIs로 넘긴다.
- Call Stack 은 싱글 스레드지만, Web APIs들은 멀티 스레드이다. ➜ 동시 작업 처리가 가능하다.
- 비동기 작업이 완료되면, Web API는 콜백 함수를 Callback Queue에 넣는다.
⚠️ Call Stack 이 비었을 때만 Queue에서 올린다!!
✏️ 콜백 함수와 프로미스 객체
■ 콜백 함수
콜백 함수
: 다른 함수에 인자로 전달되는 함수. 주로 비동기적 작업이 완료된 후에 호출되거나, 특정 이벤트가 발생했을 때 실행된다.
왜 필요할까? ➜ 비동기 방식은 순서를 보장하지 않기 때문에, 응답의 처리 결과에 의존하는 콜백 함수를 이용해서 순서를 간접적으로 끼워맞출 수 있다.
단점은? ➜ 코드 복잡도를 증가시킨다.
■ 프로미스 객체
Promise 객체
: 비동기 작업이 끝나면 그 결과에 따라 하나의 값을 반환하는 객체
Promise 객체를 사용하면, 콜백 함수를 사용할 때의 코드 복잡도를 개선할 수 있다.
- Promise 객체는
new Promise(콜백 함수)로 생성할 수 있다. new Promise(콜백 함수)에서 콜백 함수는 두 개의 매개변수를 필요로 한다.- resolve 매개변수: 비동기 작업이 성공임을 알리는 객체
- reject 매개변수: 비동기 작업이 실패임을 알리는 객체
1
2
3
4
5
6
7
8
9
const myPromise = new Promise((resolve, reject) => {
// 비동기 작업 수행
const data = fetch('URL');
if(data)
resolve(data); // 만일 요청이 성공하여 데이터가 있다면
else
reject("Error"); // 만일 요청이 실패하여 데이터가 없다면
})
위의 작업으로 만들어진 myPromise 객체는 비동기 작업이 완료된 이후에 수행될 다음 작업을 연결할 수 있다.
➜ 콜백 함수가 했던 것처럼 순서 보장을 해준다.
⚠️
new Promise가 생성되면, Promise의 매개변수로 넘겨준 콜백함수는 자동적으로 & Synchronous 하게 실행된다.
하지만, 그 내부에서는 Asynchronous 하기 때문에 비동기적으로 작업이 수행된다.
.then() 과 .catch() 메소드 체이닝을 통해 성공과 실패에 대한 후속 처리를 할 수 있다.
1
2
3
4
5
6
7
8
9
10
myPromise
.then((value) => { // 성공적으로 수행했을 때 실행될 코드
console.log("Data: ", value); // 위에서 return resolve(data)의 data값이 출력된다
})
.catch((error) => { // 실패했을 때 실행될 코드
console.error(error); // 위에서 return reject("Error")의 "Error"가 출력된다
})
.finally(() => { // 성공하든 실패하든 무조건 실행될 코드
})
.then(), .catch()의 리턴 값은 Promise 객체이다.
➜ .then()의 결과 값에 다시 .then()를 붙일 수 있다!
✏️ async/await
하지만 프로미스도 완벽한 해결책은 아니다. ➜ 지나친 then 핸들러 함수의 남용으로 인한 Promise Hell 발생 가능
async/await
: 비동기 코드를 마치 동기 코드처럼 쉽고 명확하게 작성할 수 있게 만들어 주는 키워드. Promise를 기반으로 한다.
- then, catch 메서드를 사용하지 않는다.
- function 키워드 앞에 async 를 붙이고, 비동기로 처리되는 부분 앞에 await 를 붙여준다.
아래 코드는 async/await을 사용하지 않은 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 기존 Promise.then() 형식
// 프로미스 객체 반환 함수
function delay(ms) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`${ms} 밀리초가 지났습니다.`);
resolve()
}, ms);
});
}
function main() {
delay(1000)
.then(() => {
return delay(2000);
})
.then(() => {
return Promise.resolve('끝');
})
.then(result => {
console.log(result);
});
}
// 메인 함수 호출
main();
아래의 코드는 async/await을 사용한 코드이다.
1
2
3
4
5
6
7
8
9
10
// async/await 방식
async function main() {
await delay(1000);
await delay(2000);
const result = await Promise.resolve('끝');
console.log(result);
}
// 메인 함수 호출
main();
REFERENCE
https://inpa.tistory.com/entry/JS-📚-비동기처리-Promise [Inpa Dev 👨💻:티스토리]
