오늘은 자바스크립트의 Promise
가 어떻게 동작하는지에 대해서 공부를 해보려고 합니다.
자바스크립트는 싱글 스레드 언어인데 ?
자바스크립트의 Promise
는 비동기 처리를 할 때 사용합니다. 근데 신기한 부분이 있습니다. 자바스크립트는 싱글 스레드 언어인데 어떻게 비동기 처리를 하는 걸까요 ? 싱글 스레드의 특징 중 하나인 단일 작업 흐름: 한 번에 하나의 작업만 처리할 수 있습니다. 에 따르면 한 번에 하나의 작업만 처리하는데 "하나의 작업이 끝나기도 전에 다른 작업을 실행시킨다." 라는 개념이 이해가 되지 않습니다. 우선 동기 처리와 비동기 처리일 때를 분리해서 이때 자바스크립트가 어떻게 동작하는지 살펴보겠습니다.
이 사이트에서 자바스크립트의 동작을 시각화해줘서 이해하기 편한 거 같아요 ! 근데 오류가 좀 있는거 같습니다.
동기 처리
간단하게 예시 코드로 동작을 확인해 보겠습니다.
function Test1() {
console.log("test1");
}
function Test2() {
Test1();
console.log("test2");
}
function Test3() {
Test2();
console.log("test3");
}
Test3();
이제 실행시켜볼까요 ?
영상에서 잘 나왔다 싶이 함수의 실행 컨텍스트들이 순서대로 잘 쌓이고 실행되는 모습입니다.
비동기 처리
이제는 비동기 처리일 때도 한번 확인해 볼까요 ? 우선 Promise
가 나오기 전에 비동기 처리를 위해 사용했던 setTimeout
으로 확인해 보겠습니다.
setTimeout
코드입니다.
function Test1() {
console.log("test1");
}
function Test2() {
Test1();
console.log("test2");
}
function Test3() {
setTimeout(function setTimeoutCallback() {
console.log("test3");
}, 0);
Test2();
}
Test3();
Test3 함수를 실행시키면 setTimeout의 timer가 0이니 바로 실행되지 않을까요 ? 한번 결과를 확인해 보겠습니다 !
setTimeout 처리 과정
음 setTimeoutCallback 함수가 바로 실행되지 않고 나중에 실행된 Test1, Test2 그리고 Test3이 콜스택에서 사라졌을 때 실행되는 모습입니다.
왜 이런 결과가 나올까요 ?
정답은 바로 이벤트 루프
때문입니다. 위에서 언급했다싶이 자바스크립트는 싱글 스레드입니다. 하지만 이 자바스크립트가 실행되는 브라우저 환경에서는 비동기 작업을 처리하기 위한 Web API가 있습니다.
- 테스크 큐와 마이크로 테스크 큐는 콜백 큐의 종류입니다.
- Web API를 통해 비동기 작업을 처리
- 완료된 순서대로 비동기 작업은 콜백 큐로 이동됨
- 이벤트 루프
- 이벤트 루프는 콜스택과 콜백 큐를 반복하여 확인하면서 콜스택이 비워지면 콜백 큐의 작업을 콜스택으로 옮김
이런 원리로 브라우저에서는 setTimeout을 처리합니다.
이제는 오늘 글의 주제인 Promise에 대해서 살펴보겠습니다.
Promise
Promise
는 setTimeout
으로 비동기를 처리하는 것과 어떤 점이 다를까요 ? 그저 timer를 0으로 설정하는 것을 단순화 시키고 코드를 좀 더 가독성 있게 만들어주는 시멘틱 슈가일까요 ? 한번 확인해 보겠습니다.
function Test1() {
console.log("test1");
}
function Test2() {
Test1();
console.log("test2");
}
function Test3() {
Promise.resolve("promise").then(function promiseCallback(res) {
console.log(res);
});
Test2();
console.log("test3");
}
Test3();
이제 실행시켜볼게요.
오호 뭔가 setTimeout과 비슷하게 동작하는 거 같은대요 ? 다른 점이 있다면 위에서 setTimeout을 사용한 처리는 Task Queue
에 있었는데 Promise는 Microtask Queue
에 있네요.
setTimeout vs Promise
이제 이 둘의 차이에 대해서 좀 더 비교해 보겠습니다.
이런 식으로 코드를 짜면 어떻게 실행될까요 ?
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
Promise.resolve("promise").then(function promiseCallback(res) {
console.log(res);
});
둘 다 바로 처리되는 비동기 작업이고 setTimeout이 먼저 실행됐으니 setTimeout이 먼저 콘솔에 나올까요 ?
??? 이상하게 Promise부터 콘솔에 찍히네요.
영상을 살펴보면 위에서 언급한 Microtask Queue
에 들어가 있는 promiseCallback이 먼저 콜스택으로 들어가고 그다음에 Task Queue
에 있는 setTimeoutCallback이 콜스택으로 가는 모습을 볼 수 있는데요.
이러한 이유는 Microtask Queue의 우선순위가 Task Queue보다 높기 때문입니다.
Microtask Queue가 비워지기 전까지는 Task Queue의 동작들이 콜스택으로 이동하지 않습니다.
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
setTimeout(function setTimeoutCallback() {
console.log("setTimeout");
});
Promise.resolve("promise").then(function promiseCallback(res) {
console.log(res);
});
이렇게 먼저 Task Queue에 여러 개를 쌓아도
Microtask Queue가 비워지기 전까지는 먼저 실행되지 않습니다.
Promise Method
이제 Promise에서 제공하는 정적 메서드 몇 개의 동작을 확인해 보겠습니다.
Promise.all
Promise.all
은 모든 Promise가 완료된 후에 실행되는 메서드입니다.
const promise1 = new Promise(function promiseCallback1(resolve) {
setTimeout(function setTimeoutCallback1() {
resolve("promise1");
}, 3000);
});
const promise2 = new Promise(function promiseCallback2(resolve) {
setTimeout(function setTimeoutCallback2() {
resolve("promise2");
}, 2000);
});
const promise3 = new Promise(function promiseCallback3(resolve) {
setTimeout(function setTimeoutCallback3() {
resolve("promise3");
}, 1000);
});
Promise.all([promise1, promise2, promise3]).then(function promiseAllCallback(
values
) {
console.log(values);
});
결과를 확인해 보면 3초 뒤에
이런 로그가 찍히는 것이 확인됩니다.
흠 근데 영상에는 좀 오류가 있네요. Queue는 선입선출입니다.
그리고 Web API에서 완료된 작업부터 Queue에 담기는데 영상에서는 callback 함수가 실행될 때 바로 담기는 모습이네요.
Promise들이 다 끝난 뒤에 Promise all의 callback이 실행되는 모습입니다.
Promise.race
Promise.race
은 받은 Promise 중에서 가장 먼저 완료된 것의 결과를 사용합니다.
const promise1 = new Promise(function promiseCallback1(resolve) {
setTimeout(function setTimeoutCallback1() {
console.log("setTimeoutCallback1");
resolve("promise1");
}, 3000);
});
const promise2 = new Promise(function promiseCallback2(resolve) {
setTimeout(function setTimeoutCallback2() {
console.log("setTimeoutCallback2");
resolve("promise2");
}, 2000);
});
const promise3 = new Promise(function promiseCallback3(resolve) {
setTimeout(function setTimeoutCallback3() {
console.log("setTimeoutCallback3");
resolve("promise3");
}, 1000);
});
Promise.race([promise1, promise2, promise3]).then(function promiseAllCallback(
value
) {
console.log(value);
});
이런 식으로 코드를 짜면
이런 결과가 나오는군요.
영상을 확인해 보면 (Task Queue에 완료되지 않은 작업이 쌓이는건 오류입니다.) 먼저 완료된 setTimeoutCallback3먼저 콜스택으로 오고 바로 promiseAllCallback이 Microtask Queue로 생성돼 콜스택으로 이동 후 처리가 되고 뒤에 늦게 처리되는 setTimeoutCallback1, setTimeoutCallback2가 실행되는 모습입니다.
미루고 미루던 Promise의 동작에 대해서 공부를 해보고 정리를 해봤습니다.
잘못된 정보가 있거나 오류가 있으면 피드백 주시면 감사하겠습니다.
'frontend' 카테고리의 다른 글
(React 19 + Next js 15 + Panda Css) 1. 프로젝트 세팅 (1) | 2024.06.11 |
---|---|
웹 어셈블리 찍먹 ! (0) | 2024.05.23 |
No.1 Css 라이브러리 "Panda Css" (0) | 2024.05.06 |
🚀 "우당탕탕 도서관" 프론트엔드 개발자 글쓰기 커뮤니티 2기 모집 안내 🚀 (0) | 2024.05.04 |
??? : 추상화 잘합니다. (대충 할 말 빙빙 돌려 말한다는 뜻) (0) | 2024.04.01 |