[Frontend 기술 면접 대비] 시리즈는 Frontend 개발자로 취업하기 위해 내 프로젝트 경험과 지식들을 정리한 내용이다.
요청과 그 결과가 동시에 일어난다. 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져야 한다.
- 순서에 맞춰 진행된다.
- 설계가 매우 간단하다.
- 여러 가지 요청을 동시에 처리할 수 없고 대기해야 한다.
요청과 결과가 동시에 일어나지 않는다. 요청에 따른 응답을 즉시 처리하지 않아도, 대기 시간동안 다른 요청에 대한 처리가 가능하다.
- 동기 방식보다 속도가 느리고 복잡하다.
- 자원을 효율적으로 사용할 수 있다.
JavaScript의 엔진은 다음 두 가지 요소로 구성되어 있다.
JavaScript는 Single Thread 언어이므로 단일 호출 스택이 있다. 즉, 한 번에 하나의 일만 처리할 수 있다. 스택의 사이즈를 초과했을 경우, Stack Overflow 에러가 난다.
push
)된다.pop
)된다.Call Stack에 저장되는 각 항목을 실행 맥락(Execution Context)라 한다.
하나의 작업이 완료될 때까지 기다려야하는 문제점을 해결하기 위해서 비동기 콜백(Asynchronous Callback)을 사용한다. 일이 끝나면 실행시킬 콜백 함수들은 바로 Call Stack에 push
될 필요가 없다. 이를 위해 JavaScript 실행환경(runtime)은 태스크 큐(Task Queue)를 가지고 있다.
비동기 콜백 과정
- DOM 이벤트, HTTP 요청,
setTimeOut()
등과 같은 비동기 함수는 web API를 호출한다.- Web API는 콜백 함수를 Event Queue에 넣는다.
- Task Queue는 대기하다가 Call Stack이 비는 시점에 Event Loop를 돌려 Call Stack에 콜백 함수를 넣는다.
JavaScript는 기본적으로 비동기적으로 작업을 처리하는 비동기 처리 특성을 가지고 있다.
Ajax(Asynchronous JavaScript And XML)란 서버와 통신하기 위해 XMLHttpRequest
객체를 사용하는 것을 말한다. Ajax는 비동기성 특징을 가지므로 페이지 전체를 새로고침하지 않아도 수행된다.
function getData() {
var tableData;
// ajax 통신
$.get('https://domain.com/products/1', function(response) {
tableData = response;
});
return tableData;
}
console.log(getData());
> 실행 결과
undefined
getData()
를 호출하여 출력하면 통신으로 받아온 데이터가 tableData
에 저장되어 출력되지만 undefined
가 출력된다. 이는 데이터를 요청하고 응답받을 때까지 기다려주지 않고 return tableData
가 실행됐기 때문이다. 즉, 비동기 처리가 발생했다.
setTimeout()
은 Web API의 한 종류로, 코드를 바로 실행하지 않고 지정한 시간만큼 기다렸다가 로직을 실행한다.
// #1
console.log('Hello');
// #2
setTimeout(function() {
console.log('Bye');
}, 3000);
// #3
console.log('Hello Again');
> 실행 결과
Hello
Hello Again
Bye
setTimeout()
역시 비동기 처리가 되므로 바로 다음 코드인 console.log('Hello Again');
으로 넘어갔다. 이후 3초가 지나 Bye
가 출력된다.
Callback 함수는 다른 함수의 파라미터로 넘기는 함수를 말한다. 콜백 함수가 끝나고 콜백 함수를 받은 함수가 실행되기 때문에 동기식으로 동작된다.
function findUserAndCallBack(id, cb) {
const user = {
id: id,
name: "User" + id,
email: id + "@test.com",
};
cb(user);
}
findUserAndCallBack(1, function (user) {
console.log("user:", user);
});
> 실행 결과
user: {id: 1, name: "User1", email: "1@test.com"}
findUserAndCallBack
함수는 cb
로 콜백함수를 할당받았기 때문에 cb(user)
가 실행될 때, 동기적으로 실행된다.
콜백함수로 동기적으로 처리 하기 위해서는 함수의 결과값을 리턴받으려 하지말고, 결과값을 통해 처리할 로직을 콜백 함수로 넘기는 방식으로 코딩한다.
콜백함수를 연속해서 사용하면 콜백 지옥(Callback Hell) 문제가 생긴다. 이러한 코드 구조는 가독성도 떨어지고 로직을 변경하기도 어렵다.
$.get('url', function(response) {
parseValue(response, function(id) {
auth(id, function(result) {
display(result, function(text) {
console.log(text);
});
});
});
});
Promise
와 async/await
를 이용해 이런 문제를 해결할 수 있다.
Promise는 비동기 작업의 최종 완료와 그 결과값을 나타내는 객체이다. Promise는 다음 중 하나의 상태를 가진다.
Promise 사용
new Promise()
를 사용해 Promise 객체를 생성하고 콜백 함수를 선언할 수 있다. 이때 인자는 resolve
와 reject
를 사용한다.resolve
는 결과가 성공인 Promise 객체를 반환하고, reject
는 결과가 실패인 Promise 객체를 반환한다.then
을 사용해 처리하고, 결과가 실패인 Promise 객체는 catch
를 사용해 처리한다.let myFirstPromise = new Promise((resolve, reject) => {
setTimeout(function(){
// 성공 시 resolve 사용
resolve(`Success!`);
}, 3000);
});
myFirstPromise
// 성공 시 then 사용하여 결과 처리
.then((successMessage) => {
console.log(`Yay! ` + successMessage);
});
// 실패 시 catch 사용하여 결과 처리
.catch((reason) => {
console.log('여기서 거부된 프로미스( ' + reason + ' )를 처리하세요.');
});
> 실행 결과
# 성공 시
Yay! Success!
# 실패 시
여기서 거부된 프로미스( Error: Fail )를 처리하세요.
다음 예시처럼 Promise를 사용해 콜백 지옥을 해결할 수 있는 장점이 있다.
function a() {
return new Promise({
// ...
});
}
function b() {
return new Promise({
// ...
});
}
function c() {
return new Promise({
// ...
});
}
myFirstPromise()
.then(a)
.then(b)
.then(c);
Promise 역시 여전히 콜백함수를 사용하기 때문에 가독성이 좋지 않은 문제점이 있다. 이를 해결하기 위해 async/await
을 사용한다. 함수 앞에 async
를 붙이면 해당 함수는 비동기 함수(async function)가 되고 반환되는 값은 Promise 객체가 된다. await
은 then
과 유사한 기능을 한다. await
이 붙은 메서드가 종료될 때까지 비동기 함수는 실행을 중지한다. 비동기 함수는 동기식 코드를 짜듯이 비동기 코드를 짤 수 있다는 장점이 있다.
async function hello() {
return 'Hello';
}
async function callHello() {
try {
const r = await hello();
console.log('성공: ' + r);
} catch (e) {
console.log('실패: ' + e.message);
}
}
callHello();
> 실행 결과
성공: Hellotext
비동기 함수에서는 try/catch
를 이용하여 예외 처리를 할 수 있다.
JavaScript 언어를 배우기 전에는 Python만 사용해봤기 때문에, 그 당시에는 동기/비동기 동작에 대해 걱정할 필요가 없었다. JavaScript로 frontend 개발 프로젝트를 하면서, 데이터를 받아오기 전에 페이지가 렌더링되어 빈 데이터가 출력되는 일이 굉장히 빈번히 있었다. 때문에 동기/비동기와 Promise 객체, async/await
을 끊임없이 공부하고 사용했는데, 완벽히 정리하기 어려운 개념이었다. 비동기 처리방식을 모두 사용해본 결과 비동기 함수를 사용하는 방식이 가장 코드를 수월하게 작성할 수 있었다.
함께 보면 좋은 글