비동기
비동기 처리는 정확하게 이해했다고 장담할 수 없다. 그렇기 때문에 내가 아는 선에서 최대한 설명해보겠다.
비동기 처리는 정확하게 이해했다고 장담할 수 없다. 그렇기 때문에 내가 아는 선에서 최대한 설명해보겠다.
비동기
우리가 자바스크립트 코드를 작성하고 실행하면, 먼저 실행된 일이 끝나야 다음에 실행된 일이 수행한다. 이것이 동기적으로 동작한다는 것이다. 그렇다면 비동기는 무엇일까?
비동기는 단순히 동기적으로 실행되지 않는다는 것이다. 즉, 여러 작업이 한 번에 처리될 수 있고, 먼저 처리된 작업이 먼저 결과를 내게 된다.
자바스크립트는 기본적으로 싱글 스레드로 동작한다. 하나의 스레드에서 일을 처리하는 것이다. 실행 컨택스트 스택을 생각하면 된다. 그런데, 자바스크립트에서도 비동기적인 일을 하는 경우가 있었다. 바로 이벤트 리스너다.
const btn = document.querySelector('button');
btn.addEventListener('click',()=>{
console.log('clicked');
}
console.log('done')
html파일에 버튼을 하나 만들고 위 자바스크립트 코드를 script태그에 넣어 실행해보자. 그러면 addEventListener의 콜백함수는 실행되지 않는다. 만약, 동기적으로 실행된다면, 위의 코드를 실행했을 때 clicked, done순으로 콘솔에 출력됐을 것이다. 하지만 실제로는 버튼을 누르지 않으면 clicked는 볼 수 없다. 즉, 동기적으로 실행되지 않는 코드가 생긴 것이다.
이렇게 비동기로 동작하는 코드들은 코드의 흐름과 상관없이 수행되게 된다. 그렇다면 비동기 코드는 정확히 언제 수행될까?
비동기 수행
자바스크립트에는 실행컨택스트 스택과 테스크 큐가 존재한다. 그리고 이 둘을 확인하는 이벤트 루프가 존재한다. 기본적으로 함수가 호출되면 실행컨택스트 스택에 쌓이게 된다. 그리고 끝나면 스택에서 pop이 된다. 하지만 만약 그 함수가 비동기 처리를 하는 함수라면, 실행컨택스트에 올라왔다가 테스크 큐로 비동기 처리를 하는 코드를 넘겨준 후 바로 pop되게 된다.
위의 코드로 예를 들면, btn.addEventListener가 실행컨택스트에 올라온 후 비동기로 동작되는 함수인 자신의 콜백함수를 테스크 큐에 넘겨준다. 그 후 자신은 바로 종료된다.
그렇다면 테스크 큐에 보낸 이후에는 어떻게 될까? 바로 스택이 비었는지 이벤트 루프가 계속 확인한다. 그리고 스택이 비었으면 그때 테스크 큐에서 먼저 종료된 비동기 작업을 스택에 올려준다.
이런 방식으로 비동기가 동작된다. 테스크 큐에 비동기 코드가 들어가면 내부에서 동시에 실행되고 먼저 종료된 것이 실행 컨택스트 스택이 비었을 때 비로소 결과를 보여준다고 생각하자.
위의 코드가 그렇다. console.log('done')을 하는 순간 실행 컨택스트 스택은 비어있게 된다. 그런데 테스크 큐에 넘어간 콜백함수는 아직 작업이 처리되지 않았다. 왜? 버튼을 누르지 않았으니까. 버튼을 누르게 되면 그때 콜백함수 작업이 수행되고 결과가 스택에 올라와 콘솔에 보여지게 되는 것이다.
비동기에서 발생할 수 있는 문제
자바스크립트에서 비동기가 어떻게 동작하는 지 확인했다. 알았으니 비동기로 동작하는 코드를 작성해보자.
const btn = document.querySelector("button");
const body = document.querySelector("body");
const container = document.createElement("div");
container.classList.add("container");
console.log(container);
const data = [
{ name: "user1", age: 17 },
{ name: "user2", age: 19 },
{ name: "user3", age: 20 },
{ name: "user4", age: 21 },
{ name: "user5", age: 22 },
];
btn.addEventListener("click", (e) => {
data.push({
name: `user${data.length + 1}`,
age: Math.floor(Math.random() * (25 - 15) + 15),
});
console.log(data);
// render();
});
render();
body.append(container);
function render() {
return (container.innerHTML = `${data
.map((a) => `<div>${a.name}</div>`)
.join("")}`);
}
코드를 설명하면 <button>태그 하나만 있는 html파일에 동적으로 data의 값을 가지고 name의 값을 텍스트로 가진 <div>를 만들어서 추가해주는 코드이다.
위 코드를 브라우저에서 확인하면 아래의 사진과 같다.

css작업을 하지 않았지만, 어떤 모습인지 알 수 있을 것이다.
여기서 내가 하고자 하는 것은 버튼을 눌렀을 때 data에 정보가 추가되고 추가된 정보가 브라우저 화면에도 보여졌으면 하는 것이다.
하지만 위 코드를 작성하고 버튼을 눌렀을 때 화면에는 아무런 변화가 없다. 대신 콘솔창을 봐보자

위 코드에서 클릭을 하면 데이터를 추가하고 데이터를 콘솔로 출력하는 코드가 있다. 그렇기 때문에 콘솔창에 데이터가 보이는 것인데, 화면에는 5개의 데이터만 보여지고 있지만, 실제 콘솔에 찍힌 데이터는 버튼을 한 번 눌렀기 때문에 1개가 추가되어 있는 모습이다. 버튼을 계속 누르면 누른 만큼 데이터는 추가된다. 즉, 콜백함수가 정상 작동을 하고 있는 것이다.
그렇다면 왜 화면에 보여지지는 않는 것일까?
그 이유는 바로 아까 말한 비동기는 스택이 비었을 때 결과를 보여주게 되기 때문이다. 코드를 다시 보면 render()를 통해 container에 데이터를 추가해서 body의 자식으로 추가하고 있다. 즉, 처음 함수가 실행되는 당시에는 5개의 데이터가 존재한 상태로 body에 추가되는 것이다. 그 후 스택은 비어 있게 된다.
그 다음에 클릭을 하게 되면, 이미 렌더링을 하는 작업은 끝났기 때문에 아무것도 변하지 않는 것처럼 보이게 된다.
위 문제를 어떻게 해결할 수 있을까? 바로 위 코드에서 주석을 지우는 것이다.
즉, 비동기로 수행된 함수 내에서 다시 추가된 데이터를 담은 배열을 가지고 container의 내부를 바꿔주는 작업을 하게 되는 것이다.

버튼을 누르면 아래처럼 콘솔의 보이는 데이터와 같게 화면도 한 개의 데이터가 더 보여지는 것을 볼 수 있다.
우리는 비동기 작업을 하면서 종종 브라우저에 그 변화를 반영하지 않는 경우가 있다. 그렇기 때문에 비동기 함수 내에서 그런 처리를 하는 것을 잊어버리면 안 된다.