minTech

[JavaScript] 동기(Synchronous)와 비동기(Asynchronous) 본문

JavaScript

[JavaScript] 동기(Synchronous)와 비동기(Asynchronous)

pushzzeong 2024. 2. 2. 15:09

자바스크립트의 동기와 비동기.. 복잡한 개념인 것 같아 배우면서 애를 먹었던 부분이다. 

이 두 개념에 대해 이해한대로 정리해보았다.

 

동기(synchronous)

동기적으로 실행한다는 것은 무엇을 의미하는 것일까?

➡️ 동기 실행이란 한 번 시작한 작업을 다 처리하고 나서야 다음 코드로 넘어가는 것이다. 

 

<🤷‍♂️example 🤷‍♂️ >

 

다음과 같이 변수를 선언해주어 값을 세 번 다른 값으로 할당 후에 중간중간 콘솔로 출력해보았다. 

let num = 1;
console.log(num);

num = 2;
console.log(num);

num = 3;
console.log(num);

 

 

 

자바스크립트는 동기식 언어이기 때문에 코드를 위에서 아래로 내려오면서 한줄한줄 실행한다.

따라서 결과는 1,2,3이 차례대로 출력됨을 확인할 수 있다. 

 

 

🖐️자바스크립트는 동기식 언어?

자바스크립트는 왜 동기식 언어일까? 그것은 먼저 자바스크립트의 엔진의 작동 원리를 보면 알 수 있다.

엔진은 Memory HeapCall Stack으로 구성되어있다. 

 

Memory Heap

  • 메모리 할당이 일어나는 부분이다.
  • 참조형 데이터가 저장된다. 
  • 참조형 데이터가 저장된 주소값이 콜스택의 변수 식별자 값으로 지정된다. 이는 함수 실행 시에 주소값을 통해 해당 참조값을 찾을 수 있다. 
  • 콜스택에 할당되는 변수 식별자 자체는 콜스택 상의 실행 컨텍스트의 렉시컬 환경이라는 곳에 저장된다.  

 

Call Stack 

  • 메모리에 존재하는 공간 중 하나로, 코드 실행에 따라 호출 스택이 쌓이는 곳이다.
  • 코드를 읽어내려 가면서 수행할 작업들을 밑에서부터 하나씩 쌓이고, 마지막에 쌓인 작업이 맨 먼저 실행된다.
  • 이러한 구조를 LIFO(Last in First Out)라고 한다. 

 

자바스크립트는 싱글 스레드 기반 언어이다. 즉, 호출 스택이 하나이기 때문에 한 번에 한 작업만 처리가 가능하다. 

그렇기 때문에 함수를 실행하면 함수의 기록을 맨 위에 추가해주고, 함수가 결과값을 반환하면 스택의 맨 위에 있던 함수는 제거 된다. 

 

<🤷‍♂️example 🤷‍♂️ >

 

예시를 통해 동작과정을 확인해보면 다음과 같다. 

function printOne() {
  console.log(1);
}

function printTwo() {
  console.log(2);
}

function printThree() {
  console.log(3);
}

printOne();
printTwo();
printThree();

 

1. 그림에서와 같이 먼저 printOne() 함수가 호출되면서 콜스택의 맨 위에 쌓인다.
2. printOne함수가 console.log(1)가 호출되고 콜스택의 맨 위에 쌓인다.
3. 결과값으로 1을 반환하면 스택의 맨 위에 있던 console.log(1)이 제거된다. 

 

단일 호출 스택을 통해 코드를 실행하는 것은 멀티 스레드 환경에서의 부분을 고려할 필요가 없기 때문에 단순하다. 

 

하지만 호출 스택이 하나이기 때문에 만일 하나의 복잡한 함수를 처리하느라 시간을 많이 소요하게 되면, 

후속 작업을 처리하는데 문제가 발생하거나 속도가 느려질 수 있다. 

 

이러한 문제를 해결하기 위해서 사용하는 것이 비동기 실행이다.

 

 

비동기(Asynchronous)

비동기 실행은 한 번 시작된 작업이 완료되기 전에 바로 다음코드로 넘어가고, 나중에 콜백이 실행됨으로써 작업이 마무리되는 것이다. 

 

<🤷‍♂️example 🤷‍♂️ >

 

서버에 리퀘스트를 주고, 리스폰스를 받는 fetch 함수를 이용해 서버의 성공 결과를 받아 그 값을 출력하는 코드이다.

fetch 함수의 전과 후에 "start"와 "end"의 문자열을 출력하도록 하는 코드를 추가해주었다.

console.log("start");
fetch('https://jsonplaceholder.typicode.com/posts')
      .then(response => response.json())
      .then(json => console.log(json))
console.log("end");

 

🙋 위의 url 주소는 api 테스트를 해볼 수 있는 {JSON} Placeholder 홈페이지를 통해 작성한 것입니다. 

      get 메소드를 이용해 간단한 테스트를 하기 좋은 홈페이지이니 추천드립니다!

 

 

👀 실행결과는 어떨까?

 

예상으로는 "start" 라는 문자열이 출력되고, fetch 함수가 실행되어, 서버 응답을 통해 받은 json 파일이 출력된 후에 "end"라는 문자열이 출력될 것이다. 

 

하지만 출력결과는 

 

"start"와 "end"가 출력된 후에 서버로부터 받은 json 파일을 출력한다. 

 

 

위와 같은 결과가 출력된 이유는 무엇일까?

 

코드의 동작 순서는 다음과 같다.

  1. 먼저, 처음에는 console.log("start") 함수를 실행하면서 콘솔에 'start' 문자열이 출력된다.
  2. fetch 함수의 .then 메소드를 통해 콜백함수를 등록만 한다. 
  3. 그 후, 다음 코드로 넘어가게 되고, console.log("end")가 실행되면서 콘술에 "end"  문자열이 출력된다.
  4. 서버에서부터 response가 도착하면 등록해주었던 콜백함수들이 실행된다. 

‼️ 여기서 (response) => response.text()) 와 (result) => console.log(result)가 콜백함수입니다. ‼️ 

 

이렇게 비동기 실행은 서버의 응답을 기다리는 동안 그 이후의 작업을 미리 처리하기 때문에 동기 실행보다 동일한 작업을 더 빠른 시간내에 처리가 가능하다는 장점을 갖는다. 

 

😄 단일 스레드 기반의 언어인 자바스크립트 언어가 어떻게 비동기 실행이 가능한지는

저의 이벤트 루프 글을 참고해주세요! 😄 go ➡️  https://mongsira.tistory.com/26

 

[JavaScript] 이벤트 루프(Event Loop)

🖐️시작 전에 자바스크립트는 단일 스레드 기반의 언어이다. 그렇기 때문에 요청이 동기적으로 처리되어 한 번에 한 가지 일만 처리가 가능하기 때문에 한 번에 많은 작업들을 수행할 수 가

mongsira.tistory.com

 

fetch 함수 말고도 비동기 실행되는 함수가 존재한다. 다른 함수는 무엇이 있을까?

 

1. setTimeout() : 특정 함수의 실행을 원하는 시간만큼 뒤로 미루기 위해 사용하는 함수

2. setTimeout() : 특정 콜백을 일정한 시간 간격으로 실행하도록 등록하는 함수

3. setInterval() : 특정 콜백을 일정한 시간 간격으로 실행하도록 등록하는 함수

4. addEventListener메소드 : 특정 이벤트가 발생했을 때, 실행할 콜백을 등록하는 함수

 

 

fetch 함수가 아닌 비동기 함수는 어떻게 사용할까?

 

<🤷‍♂️example 🤷‍♂️ >

// 1번
let num = 1;
console.log(num);
// 2번
setTimeout(() => {
  num = 2;
}, 0);
console.log(num);
// 3번
num = 3;

// 4번
console.log(num);

 

num이라는 이름을 가진 변수를 선언해주고, 다른 값을 3번 할당해준다. 여기서 동기적 실행 코드의 예시와 다른점은 바로 num에 2의 값을 할당할 때 setTimeout() 함수 내부에서 할당을 하였다.

 

setTimeout() 함수는 특정 시간만큼 내부의 코드 실행을 뒤로 미뤄준다. 위 코드에서는 지연시간을 0초로 지정해주었다. 

 

 

👀 실행결과는 어떨까?

 

주석으로 처리된 1번, 2번, 3번, 4번이 순서대로 실행되며, 1의 값이 할당된 num이 출력되고,

setTimeout을 통해 0초동안 시간이 지연된 후 2의 값이 할당되고 출력이 되고, 3의 값이 할당된 후,

또 이 값이 출력될 것이라고 예상된다. 

 

하지만 실행결과는 다음과 같다.

 

1이 두 번이나 출력된다. 왜 이런 결과가 나왔을까? 또한 딜레이 시간을 0ms 로 전달하였지만 왜 즉시 실행되지 않았을까?

 

브라우저는 setTimeout의 콜백을 즉시 실행하지 않고, 현재 실행중인 코드가 모두 완료된 후, 실행한다.

즉, 동기적인 작업과 현재 실행중인 태스크가 완료되면 이벤트 큐에 있던 setTimeout으로 등록된 콜백함수가 실행된다. 

 

[동작 순서]

1. 따라서 처음에는 1의 값이 할당된 num이 출력된다.

2. 그리고 setTimeout함수를 만나면 비동기 함수이기 때문에 바로 지연시간이 0ms으로 지연했다고 해도, 즉시 실행이 되지 않고, 바로 다음 코드로 넘어간다. 

3. 여기서 다음 코드는 콘솔에 num을 출력하는 것이다. 아직 num에 1말고는 아무것도 할당되지 않은 상태이기 때문에 똑같은 1의 값 출력될 것이다.  

 

🔍 구체적인 비동기 동작 원리를 간단하게 설명하자면

비동기 함수는 실행 전에 콜스택으로 가는 것이 아닌 WebAPI로 넘어가서 이벤트 루프에 의해 관리된다. 따라서 SetTimeout은 이벤트 루프가 동기작업을 모두 완료하고, 콜스택까지 비워졌다고 파악한 경우에 콜스택으로 이동된다.

그래서 아무리 지연시간을 0으로 주더라도, 바로 실행이 되지 않게 된다. 

 

 

➕ 비동기 함수와 fetch

then 메소드를 사용하여 콜백을 등록하는 fetch 함수와는 달리 이러한 비동기 함수들은 함수의 아규먼트로 콜백을 넣는다. 

다른 점이 무엇일까? ➡️ 두 개의 가장 큰 차이점은 리턴값이다.

 

비동기함수는 콜백함수를 리턴하지만, fetch 함수는 프로미스 객체를 리턴한다. 

 

 

 

 

프로미스 객체에 대해서는 다른 글에서 정리하도록 하겠습니당.. 🙏

 

 

 

📚참고자료

https://new93helloworld.tistory.com/358

https://ljtaek2.tistory.com/142