콜백으로 인한 비동기 처리의 단점
콜백 지옥(Callback hell)
function fn() {
setTimeout(() => {
console.log("하나");
setTimeout(() => {
console.log("둘");
setTimeout(() => {
console.log("셋");
}, 0);
}, 0);
}, 0);
}
fn(); // 결과 순서 => '하나', '둘', '셋'
- ES6 이전에는 콜백 방식을 이용해서 비동기 처리를 하였습니다. 하지만, 콜백 방식으로 비동기 처리를 할 경우 비동기 처리에 대한 결과를 확인하고 또 다시 비동기 처리 요청하는 과정에서 가독성이 나빠지고 에러의 처리가 힘들어지게 됩니다. 즉, 콜백 지옥에 빠지게 됩니다. ES6에서는 이러한 단점을 보완하기 위해서 Promise 방식을 도입했습니다.
에러 처리의 한계
try {
setTimeout(() => {
throw new Error("Error!");
}, 1000);
} catch (err) {
console.error("catch", e);
}
- 비동기 처리를 위한 콜백 패턴의 문제점 중 하나로는 에러 처리가 곤란하다는 것입니다. setTimeout으로 전달된 콜백 함수는 호출자가 setTimeout 함수가 아닙니다. 비동기 처리방식에 따라서 추후 이벤트 루프가 실행 컨텍스트 스택에 push되어 실행되는데 따라서 콜백 함수가 발생시킨 에러는 setTimeout을 감싼 catch 블록에서 캐치되지 않습니다.
Promise
- 콜백 방식으로 처리할 경우 위와 같은 단점이 있기 때문에 ES6에서는 콜백 방식이 아닌 Promise 방식을 통해 비동기 처리를 구현할 수 있습니다.
Prmoise 생성
const promise = new Promise((resolve, reject) => {
// 비동기 처리
if (true) {
resolve(true);
} else {
reject(false);
}
});
- Promise는 ES6에 도입된 ECMAScript 사양에 정의된 표준 빌트인 객체입니다. Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수를 인수로 전달받는데 이 콜백 함수는 resolve와 reject 함수를 인수로 전달받습니다.
- 비동기 처리를 위해 콜백 함수를 정의할 때 결과에 대한 응답을 하기 위해 매개변수로 resolve, reject를 받도록 정의합니다. 그리고 Promise 생성자 함수의 인자로 전달합니다. 처리 성공 시 결과와 함께 resolve를 호출하고, 실패 시 결과와 함께 reject를 호출하면 됩니다.
Promise 상태와 결과
Promise 상태
pending | 비동기 처리가 아직 수행되지 않은 상태 | 프로미스가 생성된 진후 기본 상태 |
fulfilled | 비동기 처리가 수행된 상태(성공) | resolve 함수 호출 |
rejected | 비동기 처리가 수행된 상태(실패) | reject 함수 호출 |
- Poromise는 위와 같이 현재 비동기 처리가 어떻게 진행되고 있는지를 나타내는 상태(status) 정보를 갖습니다. 생성된 직후의 Promise는 기본적으로 pending 상태입니다. 이후 비동기 처리가 수행되면 비동기 처리 결과에 따라 fulfilled 또는 rejected 상태가 됩니다. 그리고 fulfilled, rejected 상태를 settled 상태라고 합니다.
Promise 처리 결과
- Promise 처리 후 결과는 [[PromiseStatus]], [[PromiseResult]] 내부 슬롯에 저장됩니다.
Promise의 후속 처리 메서드
- Promise 비동기 처리 상태가 변화하면 이에 따른 후속 처리를 할 수 있습니다. 예를들어 Promise의 상태가 fulfilled, rejected 냐에 따라서 후속 처리해야 할 로직이 다른데, 이를 위해서 Promise는 then, catch, finally 메서드를 제공합니다.
- Promise의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출됩니다. 그리고 Promise 처리결과 또한 콜백 함수의 인자로 전달됩니다. 모든 후속 처리 메서드는 Promise를 반환하며, 비동기로 동작합니다.
Promise.prototype.then
const t = new Promise((resolve, reject) => resolve("Hello"));
t.then(
(result) => console.log("fulfilled"),
(result) => console.log("rejected")
);
- then 메서드는 두 개의 콜백 함수를 인자로 받으며 첫 번째 인자는 fulfilled 상태가 되었을 때, 호출할 콜백 함수를 두 번째 인자는 rejected 상태가 되었을 때 호출할 콜백 함수를 의미합니다.
promise.prototype.catch
const t = new Promise((resolve, reject) => resolve("Hello"));
t.then((result) => console.log("fulfilled")).catch((result) =>
console.log("rejected")
);
- catch 메서드는 한 개의 콜백 함수를 인수로 전달받습니다. catch 메서드의 콜백 함수는 Promise가 rejected 상태인 경우만 호출됩니다. 그리고 then과 연계해서 사용할 수 있습니다.
promise.prototype.finally
const t = new Promise((resolve, reject) => resolve("Hello"));
t.then((result) => console.log("fulfilled"))
.catch((result) => console.log("rejected"))
.finally(() => console.log("finally"));
- finally 메서드는 한 개의 콜백 함수를 인수로 전달 받으며 Promise의 성공, 실패 여부와 상관없이 무조건 한 번 호출됩니다. finally 메서드는 try/catch에서의 finally와 같이 상태와 상관없이 공통적으로 수행해야 할 처리 내용이 있을 때 유용합니다.
Promise의 에러 핸들링
- Promise에서 에러처리는 then 메서드의 두 번째 인자로 전달된 콜백 함수 또는 catch 메서드에 인자로 전달된 콜백 함수를 통해서 에러 핸들링을 할 수 있습니다.
Promise 체이닝
const t = new Promise((resolve, reject) => resolve("Hello"));
t.then((result) => {
return new Promise((resolve, reject) => resolve(`${result} World`));
})
.then((result) => console.log(result))
.finally(() => console.log("finally"));
- 콜백 함수를 이용한 비동기 처리는 비동기 처리 결과에 따른 콜백 함수가 중첩될수록 가시성이 매우 떨어집니다.
- 콜백 지옥의 문제를 Promise와 Promise 메서드를 이용해서 위 코드와 같이 이전 비동기 처리 결과에 따른 비동기 후속 처리를 다시 요청 후 Promise 메서드를 통해 결과를 처리할 수 있습니다. 이를 Promise 체이닝이라고 합니다.
- 위와 같은 처리가 가능한 이유는 비동기 후속 처리를 위한 Promise 메서드 내부에서 Promise가 아닌 값을 반환하더라도 그 값을 암묵적으로 resolve하여 프로미스를 생성해 반환합니다. 그리고 반환된 Promise를 통해서 Promise 후속 처리 메서드를 호출하여 체이닝을 통해 비동기 처리 결과를 처리할 수 있습니다.
마이크로테스크 큐
setTimeout(() => console.log(1), 0);
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
- Promise의 후속 처리 메서드도 비동기로 동작하므로 1 -> 2 -> 3의 순으로 출력될것 같지만 2 -> 3 -> 1 순서로 출력됩니다. 그 이유는 Promise의 후속 처리 메서드의 콜백 함수는 테스크 큐가 아니라 마이크로테스크 큐에 저장되기 때문입니다. 마이크로테스크 큐는 테스크 큐보다 우선순위가 높습니다. 그렇기 때문에 이벤트 루프는 콜 스택이 비면 먼저 마이크로테스크 큐에서 대기하고 있는 함수를 가져와서 실행 컨텍스트 스택에 push하며 마이크로테스트 큐가 비었을 때 테스크 큐에서 대기하고 있는 함수를 가져와 실행합니다.
'Programming Language > JavaScript' 카테고리의 다른 글
Javascript의 async/await 사용법 (0) | 2022.12.19 |
---|---|
Javascirpt fetch 사용 방법 (0) | 2022.12.19 |
JSON(Javascript Object Notation)이란? (0) | 2022.12.19 |
Javascript의 비동기 처리 방식 (0) | 2022.12.19 |
Set과 Map (0) | 2022.12.19 |