자바스크립트란
자바스크립트 언어(이하 JS)는 객체 기반의 스크립트 언어로, 동적이며, 타입을 정의할 필요 없는 인터프리터 언어이다. 또한 객체 지향형 프로그래밍과 함수형 프로그래밍을 모두 표현할 수 있는 언어이다.
JS는 클라이언트 사이드 언어로 개발되었으며 JAVA와는 관계없는 별도의 언어이다. 클라이언트 사이드 언어 특성상 주로 브라우저에서 실행되며, 브라우저의 자바스크립트 가상 머신이란 엔진이 JS를 동작시키게 된다.
엔진의 종류는 다양하며, 그 예시는 아래와 같다.
- V8 - Chrome, Opera
- SpiderMonkey - Firefox
- ChakraCore - Microsoft Edge
- SquirrelFish - Safari
- Trident, Chakra - IE
그러나 자바스크립트가 위 엔진만을 바탕으로 동작하는 것은 아니다. Web API, Event Loop, Task Queue 등이 자바스크립트 엔진과 함께 JS를 동작시킨다. 그리고 이러한 자바스크립트가 구동되는 환경을 총칭해 자바스크립트 런타임이라 칭한다.
그러나 초기에 클라이언트 사이드로 설계되었던 JS는 자바스크립트 런타임을 대체하는 Node.js의 등장으로 인해 서버 사이드. 즉 브라우저를 켜지 않더라도 로컬에서 JS를 사용 가능해졌다.
현재에 이르러 JS는 서버 사이드, 클라이언트 사이드 모두에 사용되고 있는 언어이다.
자바스크립트 | 동기, 비동기
JS 엔진은 기본적으로 Single Thread. 즉 한 번에 여러 작업을 수행할 수 없고 한 번에 한 작업만을 수행 가능한 언어이다.
또한 기본적으로 자바스크립트는 코드를 위에서부터 아래로 하나씩 실행하는 동기적인 방식을 택한다
이때 문제가 발생한다. 만약 일련의 코드 흐름이 존재할 때, 중간에 많은 시간을 요하는 작업이 있다면 동기적인 방식으로는 JS를 사용하는 페이지의 속도가 현저히 느려질 수밖에 없다.
이때 등장하는 개념이 비동기 개념이다.
만약 자바스크립트가 실행되는 과정 중, DB에서 데이터를 검색하거나 하는 등의 비교적 많은 시간을 요하는 작업을 분류해 백그라운드로 전달하고 시간이 적게 걸리는 작업을 모두 수행한 뒤, 해당 작업을 마저 수행하는 것이다.
자바스크립트 | 동기 방식의 원리
자바스크립트는 앞서 설명했듯, 기본적으로 동기적 방식을 채택한다. 이 과정에서 JS 엔진의 Call Stack을 통해 동기적 방식을 구현해 낸다.
JS 엔진은 코드를 읽어 내려가며 수행할 작업들을 코드의 흐름대로 차례대로 Call Stack에 push 한 후, 콜 스택의 위에서부터 차례로 pop 하면서 함수를 실행시킨다. 또한 자바스크립트는 싱글스레드 언어이므로, 하나의 콜 스택을 갖는다.
function add(x, y) {
return x + y;
}
function calc(x, y) {
var result = add(x, y);
console.log(result);
}
calc(2, 3); // 5
위 JS 코드가 실행될 때의 자바스크립트 내 콜 스택은 아래 사진과 같이 구성된다.
아래에서 위로 push 되어 쌓이고, 위에서 아래로 pop 되며 함수를 실행하는 원리이다. 이 원칙을 LIFO (Last In, First Out) 원칙이라 한다.
본래는 JS의 글로벌 실행 콘텍스트인 main 함수가 Call Stack의 과정에 추가되어 Call Stack이 완전히 비는 과정이 한 단계가 더 존재하나, 가독성을 위해 main 함수는 생략하였다.
자바스크립트 | 비동기 방식의 원리
비동기적인 방식은 JS 엔진뿐 아닌 Web API, Event Loop, Task Queue에 의해 완성되며 각각에 대한 설명은 다음과 같다.
- Web API
브라우저에서 제공되는 API로 JS 엔진에 내장되지 않지만 브라우저 환경에서 자바스크립트 코드가 상호작용이 가능하도록 기능을 제공한다. HTTP 요청 (ajax 메서드), DOM 이벤트뿐 아닌 그 외 console, alert와 같은 여러 함수들을 제공하며, 이를 통해 디버깅 정보 출력, 경고 표시등의 작업이 가능하다.
- Event Loop
자바스크립트의 동시성 모델을 지원하는 메커니즘으로 Call Stack과 후술 할 Task Queue를 관리하는데, Call Stack이 비었다면 Task Queue에서 대기 중인 콜백 함수를 Call Stack으로 이동시켜 이를 실행한다.
- Task Queue
비동기 작업의 콜백 함수가 대기하는 공간으로 Event Loop가 정한 순서대로 콜백 함수가 위치해, 이를 콜백 큐 (Callback Queue)라고도 칭한다. Call Stack에 더 이상 함수가 존재하지 않는다면 이 Task Queue에 저장된 콜백 함수가 Call Stack으로 이동해 실행되게 된다.
console.log("1");
setTimeout(function () { // 3초 후 실행
console.log("2");
}, 3000);
console.log("3"); // 1 3 2
위와 같은 코드가 존재할 때, setTimeout은 Web API의 비동기 함수로 자바스크립트는 비동기적으로 동작해 최종적으로 1 3 2를 출력하게 된다.
이때의 내부 변화는 다음과 같다.
1. console.log(1)이 먼저 Call Stack으로 들어가 처리되며 1을 콘솔에 출력한다.
2. SetTimeout은 Web API의 비동기 함수이므로 JS 엔진이 아닌 Web API가 처리하기에 후술 할 callback 함수를 전달하고 Web API는 setTimeout을 처리해 3초를 카운트한다.
3. Call Stack이 비었기 때문에 console.log(3)이 들어와 콘솔에 3을 출력한다.
4. Web API에서 3초간의 카운트가 종료되었다면, Event Loop가 Task Queue로 해당 함수를 이동시킨다.
5. Call Stack이 비어있는 것을 Event Loop가 확인하면 Call Stack으로 함수를 전달한다.
6. Call Stack에 전달된 console.log(2)가 실행되며 콘솔창엔 최종적으로 1 3 2 순서대로 출력되며 종료된다.
자바스크립트 | callback
자바스크립트에선 독특한 문법이 있다.
일반적인 언어의 함수는 함수의 파라미터로 변수나 값을 받지만, 자바스크립트의 경우엔 함수도 변수처럼 다루기 때문에 함수의 파라미터로 함수를 받아 내부에서 실행시키는 것이 가능하며
이러한 방식으로 전달되는 함수를 callback 함수라 칭한다.
function first(callback) {
console.log(1);
callback();
}
function second() {
console.log(2);
}
first(second); // 1 2
위 코드에선 first 함수의 인자로 second 함수를 콜백함수로 전달하고 있는데, 이를 통해 first 함수의 내부에선 second 함수가 실행되어 first와 second 함수가 순차적으로 수행되게 된다.
콜백 함수를 사용하는 이유가 바로 이 순차적인 수행에 있다.
일반적인 함수의 경우 콜백 함수 사용 대신 순차적으로 함수를 실행시켜도 문제가 발생하지 않지만,
만약 비동기 작업을 다루는 경우,
이 콜백 함수를 사용해 태스크가 끝나기 전에 다른 함수가 실행되지 않도록 막고 순차적인 함수의 실행을 돕는다.
다만 이 콜백 함수가 과도하게 중첩될 경우 코드의 가독성이 매우 떨어지는 이른바 콜백 지옥이 발생해 주의가 필요하다.
자바스크립트 | promise
위에서 다뤘던 콜백 함수가 지나치게 중첩되는 콜백 지옥 현상과 기타 콜백 체계에서의 문제를 해결하기 위해 2015년 표준인 ES6에 채택된 개념이 바로 Promise 개념이다.
이 Promise는 그 이름처럼 약속을 의미하는데, 이는 비동기 작업의 결과물을 후에 전달받겠다는 약속을 담은 객체이기 때문이며, 그 약속이 바로 비동기 작업이 성공하면 결과 값을, 실패하면 그에 맞는 에러를 반환하겠다는 것이다.
다시 말해 Promise 객체는 향후에 사용할 값을 생산하는 객체로 비동기 작업의 여부에 따라 세 가지의 상태를 가지며,
한번 fulfilled나 rejected 상태가 되면, 더 이상 상태는 변하지 않는다.
- pending : 비동기 처리 로직이 완료되지 않은 상태
- fulfilled : 비동기 처리가 완료되어 promise가 결과 값을 반환해 준 상태
- rejected : 비동기 처리가 실패하거나 오류가 발생한 상태
new Promise(executor)
기본적으로 프로미스는 위 생성자 함수를 통해 생성되며, executor 함수는 JS에서 자체 제공하는 콜백인 resolve와 reject를 인자로 받는다.
let promise = new Promise(function(resolve, reject) {
// executor
});
따라서 프로미스 객체는 이러한 형식으로 선언, 호출할 수 있다.
이때의 resolve, reject 둘 중 하나는 반드시 호출되어야 하며, 각각의 의미는 아래와 같다.
- resolve :
비동기 작업이 성공적으로 완료, 즉 프라미스 객체의 상태가 fulfilled일 때 호출, 이때 전달되는 인자는 Promise의 결과 값이 됨.
- reject :
비동기 작업이 실패 혹은 에러가 발생했을 때, 즉 프라미스 객체의 상태가 rejected일 때 호출, 이때 전달되는 인자는 에러의 원인이나 에러 객체가 됨.
또한 이 Promise 객체는 .then .catch .finally로 사용 가능하며 각각 resolve가 반환되었을 때, rejecte가 반환되었을 때, Promise가 종료되었을 때 실행된다.
const promise_success = new Promise(function(resolve, reject) {
const success = true; // 작업 성공 여부 : 성공
if (success) {
resolve("success");
} else {
reject("failed");
}
});
const promise_fail = new Promise(function(resolve, reject) {
const success = false; // 작업 성공 여부 : 실패
if (success) {
resolve("success");
} else {
reject("failed");
}
});
console.log("start");
promise_success // success = true 이므로 .then 실행
.then((value) => {
console.log("Promise_success:",value);
})
.catch((error) => {
console.log("Promise_success:",error);
})
promise_fail // success = false 이므로 .catch 실행
.then((value) => {
console.log("Promise_fail:",value);
})
.catch((error) => {
console.log("Promise_fail:",error);
})
console.log("end");
위 코드에선 의도적으로 success와 fail을 트리거하는 함수 promise_success와 promise_fail 두 함수를 정의한 다음, .then문과 .catch문을 트리거하는 코드이며 실행 결과는 아래와 같다.
자바스크립트 | async / await
Promise 문법을 통해 비교적 간단하게 비동기 체인을 구현 가능했으나 이마저도 .then 체이닝 등이 중첩될 경우 가독성이 떨어진다는 문제가 존재했다.
이 문제를 해결하기 위해 새로운 문법인 Async / Await가 2017 표준 버전인 ES8에서 채택되었다. Async / Await 문법은 마치 동기 코드처럼 작성하는 것이 가능해 콜백, Promise 문법보다 더 가독성과 유지보수성이 향상되었다.
async function func() {
...
}
가장 최근의 문법답게 사용법 또한 간결해졌다. 만약 비동기적인 함수를 정의하고 싶을 때, 함수 앞에 async를 붙여주면 해당 함수가 Promise를 반환하는 함수임을 선언하는 것으로,
반환 값이 Promise 생성 함수가 아니더라도 반환 값을 Promise 객체에 넣는 것이 가능하다.
또한 비동기 함수 안 Promise를 반환하는 함수 앞에 await를 붙이면 해당 Promise 상태가 바뀔 때까지 대기해, 상태가 바뀌기 전까진 다음 연산을 수행하지 않는다.
async function func() {
try {
const result = await otherFunc();
console.log(result);
} catch (err) {
console.error('error');
}
}
위 예시코드처럼 await를 사용함으로써 otherFunc가 실행되기 전까지 console.log가 실행되지 않게 할 수 있다.
'Dev' 카테고리의 다른 글
[Oracle Cloud] 오라클 클라우드 / 로그인부터 웹 서버 띄우기까지 (0) | 2025.04.01 |
---|---|
[JS] 자바스크립트 기본 개념 / 변수 선언 방식 (0) | 2025.03.26 |
웹 사이트 내에서 문제 도커 컴포즈 파일 빌드하기 (0) | 2025.02.27 |
pm2 사용하여 Node.js 백그라운드 구동하기 (0) | 2025.02.27 |
docker-compose.yml 파일 생성하기 / nginx.conf 파일 정의하기 (0) | 2025.02.27 |