자바스크립트는 기본적으로 싱글 스레드 언어이다.
이렇게 했을 때의 문제점이 있는데, 바로 실행 시간이 긴 하나의 함수를 뒤의 함수가 계속해서 기다리는 경우가 생긴다는 것이다. CPU 스케줄링에서 FIFO 방식의 부작용을 그대로 상상하면 된다. 사용자 경험을 위한 언어인 자바스크립트 특성상 이는 치명적인 단점이다.
실제로는 micro task, macro task 등 조금 더 복잡한 구조를 갖고 있지만, 코드를 처리하는 큰 그림은 이렇다고 보면 된다.
해당 사이트에서 원하는 코드를 시뮬레이션 해볼 수 있다.
다수의 비동기 처리를 순서대로 처리하는 경우가 있을 수 있다.
예를 들어 비동기 함수 A, B, C ... 가 있는데 A의 결과값을 B가 필요로 하고, B의 결과값을 C가 필요로 하고 ... 등
실제 서비스에서는 그런 상황이 많이 발생할 것이다.
이러한 로직의 경우, A의 콜백함수로 B를 호출하고, B의 콜백함수로 C를 호출하는 식으로 구현할 수 있다.
function taskA(a, b, cb) {
setTimeout(() => {
const res = a + b;
cb(res);
}, 2000);
}
function taskB(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res);
}, 1000);
}
function taskC(a, cb) {
setTimeout(() => {
const res = a * 2;
cb(res);
}, 1500);
}
// 콜백을 중첩해서 다수의 비동기 처리를 순서대로 처리 -> 콜백 지옥 -> Promise 객체 등장 !!
taskA(1, 2, (a_res) => {
console.log("A:", a_res);
taskB(a_res, (b_res) => {
console.log("B:", b_res);
taskC(b_res, (c_res) => {
console.log("C:", c_res);
});
});
});
console.log("End-of-code");
위와 같이 구현 가능하다.
근데 실제 콜백이 콜백을 부르는 로직이 계속 중첩되면 다음과 같은 무시무시한 형태의 코드가 짜여질 수도 있다.
그냥 개발할 맛 뚝 떨어지는 비주얼... 극악의 가독성이다.
그래서 보다 깔끔하게 코드를 작성하고, 효율적으로 로직을 관리하기 위해 Promise 객체가 도입되었다.
Promise는 자바스크립트에서 비동기 작업을 표현하기 위한 객체이다.
이 객체는 미래의 어느 시점에 결과를 제공하는 작업 그 자체를 나타낸다.
Promise 객체는 내부적으로 주로 다음과 같은 구성 요소를 가지고 있다.
1. 상태 (State): Promise의 현재 상태를 나타내며, 세 가지 중 하나이다:
비동기로 처리할 특정 작업에 대해서, 저 3가지 상태에 따라 적절한 콜백함수를 호출함으로써 실행 흐름을 컨트롤할 수 있다.
2. 결과 (Result): Promise의 작업 결과이며, 결과는 다음 두 가지 중 하나이다:
이는 결과값은 Promise 객체 수행 이후 다음 함수에 넘겨주거나 하는 식으로 쓰인다.
3. 처리 메서드 (then, catch, finally): Promise에 대한 반응을 정의하는 메서드들이다.
/*
[Promise 객체 생성하기]
1. 비동기 작업 자체인 Promise()를 저장할 asyncTask 상수를 선언
2. new Promise() 생성자를 이용하여 Promise 객체 생성
3. Promise 생성자에 실행 함수(executor function)를 인자로 넘겨줌
*/
function isPositiveP(number) {
// 실행자. 비동기작업을 실질적으로 수행
const executor = (resolve, reject) => { // 결과에 따라 각기 다른 콜백함수 호출
setTimeout(() => {
if (typeof number === 'number') {
resolve(number >= 0 ? "양수" : "음수"); // 성공 -> resolve
} else {
reject("주어진 값이 숫자형 값이 아닙니다."); // 실패 -> reject
}
}, 2000);
}
const asyncTask = new Promise(executor);
return asyncTask;
}
/*
[Promise 객체 사용]
1. Promise 객체의 비동기 처리 결과를 변수에 담기
2. Promise 객체에 사용 가능한 then, catch 함수로 비동기 처리 핸들링
*/
const res = isPositiveP(10);
res.then((res) => {
console.log("success:", res);
}).catch((err) => {
console.log("fail:", err);
});
Promise 객체 생성 방법:
Promise 객체 사용 방법:
Promise 객체를 체인처럼 연결하는 방식을 사용하면 위에서 언급된 콜백 지옥을 해결할 수 있다.
function taskA(a, b) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a + b;
resolve(res);
}, 2000);
})
}
function taskB(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1000);
})
}
function taskC(a) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const res = a * 2;
resolve(res);
}, 1500);
})
}
taskA(5, 1)
.then((a_res) => {
console.log("A result:", a_res);
return taskB(a_res);
}).then((b_res) => {
console.log("B result:", b_res);
return taskC(b_res);
}).then((c_res) => {
console.log("C result:", c_res);
})
/*
(기존 콜백 지옥 함수)
taskA(1, 2, (a_res) => {
console.log("A:", a_res);
taskB(a_res, (b_res) => {
console.log("B:", b_res);
taskC(b_res, (c_res) => {
console.log("C:", c_res);
});
});
});
*/
연쇄적인 비동기 작업이 많아질수록 Promise를 이용한 코드가 더 깔끔해진다.
Promise를 이용한 위 코드의 동작 방식은 다음과 같다.
가비지 컬렉션(Garbage Collection) (0) | 2024.03.24 |
---|---|
JavaScript | 스프레드 연산자, forEach, map 기본 개념 (1) | 2024.01.09 |
JavaScript | JS 엔진과 런타임 환경의 구조 (스택, 힙, 이벤트루프 ..) (0) | 2024.01.04 |
JavaScript | 비동기 처리 | async & await 키워드 (1) | 2024.01.01 |
JavaScript | V8 엔진과 런타임 환경의 기본 개념 (0) | 2024.01.01 |