자바스크립트의 언어 특성
자바스크립트는 싱글 스레드 언어이면서 논블로킹 언어입니다. 싱글스레드란 한 번에 하나의 작업만 처리할 수 있다, 즉 일하는 사람이 1명이라는 이야기입니다. 하나의 콜 스택을 가지고 있으면서 이 스택에서 하나하나씩 처리를 합니다. 두 개의 이상의 작업을 동시에 처리할 수 없으며 하나의 작업이 완료되어야 다음 작업을 수행합니다. 하지만 자바스크립트는 비동기 작업을 통해 여러 가지 일을 수행할 수 있게 됩니다.
싱글스레드인데 동시에 다른 작업을 할 수 있는 이유 (Non-blocking)
비동기 방식을 통한 긴 작업을 하는 동안 짧은 작업들을 같이 처리합니다. (ex: 파일 다운로드, 네트워크 요청)
자바스크립트 엔진은 메모리 힙(우리가 만든 데이터가 저장되는 공간), 콜 스택을 가지고 있습니다.
웹 API와 태스크 큐, 이벤트 루프는 자바스크립트 엔진 외부인 브라우저 또는 Node.js와 같은 자바스크립트 런타임 환경에서 제공
- 콜 스택(Call Stack): 실행할 함수나 코드를 처리하는 목록, LIFO(Last In First Out) 구조
- 태스크 큐(Task Queue): 타이머, 비동기 I/O(입출력), DOM 이벤트 핸들러
- 마이크로태스크 큐(Microtask Queue): 프로미스(Promise), async / await
- 이벤트 루프(Event Loop): 콜 스택이 비어있는지 확인하고 비동기 작업이 완료되면 마이크로태스크 큐 또는 태스크 큐에 있는 작업을 콜 스택으로 이동시켜 준다. (마이크로태스크 큐와 태스크 큐 중 우선순위는 마이크로태스크 큐)
이벤트 루프는 신호등 같은 역할입니다. 콜 스택이 비는 순간부터 태스크 큐에 있는 FIFO(First In First Out) 구조로 오래된 작업부터 콜 스택으로 내보내 실행시켜줍니다. 이러한 동작 방식으로 이벤트 루프와 태스크 큐를 통해 비동기 작업을 처리하여 논블로킹을 지원합니다.
비동기를 동작하는 코드를 동기적으로 처리하는 법
예시 1 콜백 함수
- 사용방법
function start() {
setTimeout(() => {
console.log("start");
}, 1000)
}
start();
console.log("end");
// 출력 순서
// end
// start
// setTimeout은 비동기 함수로 태스크 큐로 빠져 end 출력 후 start 가 출력됨
function start(callback) {
setTimeout(() => {
console.log("start");
callback();
}, 1000)
}
start(() => console.log("end"));
// 출력 순서
// start
// end
- 콜백 함수의 문제점
여러 개의 작업을 처리하는 경우 콜백 지옥에 빠질 수 있다.
콜백 지옥은 비동기 작업이 서로 연결되어 있거나 순차적으로 실행되어야 할 때 함수가 중첩되어 코드가 깊어지고 이로 인한 가독성의 문제가 생기고 유지보수하는데 문제가 생긴다.
function first(callback) {
setTimeout(() => {
console.log("first");
callback();
}, 1000);
}
function second(callback) {
setTimeout(() => {
console.log("second");
callback();
}, 1000);
}
function third() {
setTimeout(() => {
console.log("third");
}, 1000);
}
first(() => {
second(() => {
third();
});
});
예시 2 Promise
콜백 지옥을 해결하기 위한 Promise의 등장
Promise란 비동기 처리에 사용되는 자바스크립트 객체로 비동기 작업의 성공 또는 실패와 그 값을 나타낸다.
- 사용방법
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 1000);
});
promise
.then((message) => {
console.log(message);
})
.catch((errorMessage) => {
console.log(errorMessage);
})
.finally(() => {
console.log("finish"); // 성공, 실패 여부 상관없이 실행
})
// 출력 순서
// 성공
// finish
- 위에 콜백 함수로 사용했던 방식을 Promise로 변환
function first() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("first");
resolve();
}, 1000);
});
}
function second() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("second");
resolve();
}, 1000);
});
}
function third() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("third");
resolve();
}, 1000);
});
}
first()
.then(() => second())
.then(() => third())
.catch((error) => console.log(error));
콜백 함수는 엄연히 말하면 비동기를 순차적으로 처리하기 위한 일종의 편법 같은 것이라 Promise 객체를 사용하는 것이 좋지만 이 방식도 결국 메서드 체이닝을 통한 연속적인 처리는 가독성에 좋지 않습니다.
예시 3 async / await
- 사용 방법
const promise = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("성공");
}, 1000);
})};
async function getData() {
try {
const data = await promise();
console.log(data);
} catch(error) {
console.log(error);
}
};
getData();
결론
그렇다고 해서 async / await이 무조건 정답이라고 할 수는 없을 거 같습니다.. callback 방식은 별 다른 키워드 없이 단순하게 구현할 수 있다는 장점이 있으며 콜백 지옥의 정도의 복잡한 코드가 아니라면 단순하게 사용하기 좋다. (ex. Node.js Express 프레임워크)
복잡한 비동기 작업을 처리한다면 Promise를 이용한 처리 방식을 이용하는 게 좋을 거 같습니다.
'JAVASCRIPT' 카테고리의 다른 글
[Javascript] Proxy 객체 (0) | 2025.01.15 |
---|---|
[Javascript] requestAnimationFrame과 setInterval의 차이 (0) | 2024.10.22 |
[Javascript] 자바스크립트는 컴파일 언어? 인터프리터 언어? (0) | 2024.09.04 |
[Javascript] 자바스크립트의 비트 연산자 (Bitwise Operators) (0) | 2024.09.04 |
[Javascript] 자바스크립트의 논리 연산자 (Logical Operators) (0) | 2024.08.30 |