[TIL] 동기 & 비동기

열심히하시는개발자·2021년 3월 21일
0
post-thumbnail

1️⃣ 동기 방식(Synchronous)

요청이 들어온 순서에 맞게 하나씩 처리하는 방식이고 순서에 맞춰 진행되는 장점이 있지만, 여러 가지 요청을 동시에 처리할 수 없다.

2️⃣ 비동기 방식(Asynchronous)

하나의 요청에 따른 응답을 즉시 처리하지 않아도, 그 대기 시간동안 또 다른 요청에 대해 처리 가능한 방식이고 여러 개의 요청을 동시에 처리할 수 있는 장점이 있지만 동기 방식보다 속도가 떨어질 수 있다.

function printMe() {
  console.log('1');
}

setTimeout(printMe, 1000);
console.log('2');

// 결과
// 2
// 1

자바스크립트의 싱글 스레드와 동시성

자바스크립트는 싱글 스레드 기반 언어이다. 이는 하나의 Call stack을 가진 한 번에 한가지 일만 수행할 수 있다. 그러나 자바스크립트에서는 setTimeout함수, callback함수 등은 비동기적으로 수행된다. Call stack이 하나인 자바스크립트에서 동시성이 지원 가능한 이유는 Event Loop 때문이다.

스레드

  • 프로세스 내 실행단위
  • 하나일 경우 싱글 스레드, N개일 경우 멀티스레드라고 한다.

비동기 요청을 처리하는 방법

자바스크립트 엔진은 단일 호출 스택을 사용하고 요청이 들어온 순서대로 한가지 일만 수행할 수 있다. 비동기 요청은 자바스크립트 엔진이 아닌 자바스크립트 엔진을 구동하는 런타임 환경(브라우저, Node.js)가 담당한다.

런타임 환경

Web API(Browser API)

  • DOM
  • AJAX
  • Timeout

Event Loop

위 그림처럼, 자바스크립트 엔진 이외에도 자바스크립트에 관여하는 다른 요소들이 많다. DOM, Ajax, setTiemout 과 같이 브라우저에서 제공하는 API 들을 Wep API라고 한다.

호출 스택(Call stack)
호출 스택은 기본적으로 우리가 프로그램 상에서 어디에 있는지를 기록하는 자료구조이다. 만약 함수를 실행하면 해당 함수는 호출 스택의 가장 상단에 위치하고 함수의 실행이 끝날 때 함수를 호출 스택에서 제거한다.


function multiply(x, y) {
 return x * y; 
}
function printSquare(x) {
 var s = multiply(x, x);
  console.log(s)
}
printSquare(5);

처음 엔진이 이 코드를 실행하는 시점에는 호출 스택이 비어있지만, 코드가 실행되면서 호출 스택은 아래와 같이 변한다.

Step 1     
printSquare(5);

Step 2
multiply(x,x);
printSquare(5);

Step 3
console.log(s);
printSquare(5);

Step 4
printSquare(5);

Step 5
비어있음

3️⃣ 비동기 방식 👉🏻 동기 방식

Javascript는 대부분 동기 방식으로, 처리 요청을 받으면 알려만 주고 바로 다음으로 넘어간다. 주로 통신 쪽 처리할 때 많이 겪는 문제가 있는데, 서버 쪽으로 요청을 보내고 결과 값을 받아온 후 화면에 출력을 해야하는 작업이 있지만 비동기 처리방식 때문에 처리 결과가 오기도 전에 화면에 출력을 해버리기 때문에 빈 화면을 보게 되는 경우가 발생한다.

그렇기 때문에 Callback 방식으로 어떤 작업을 요청하면 함수에 등록을하고, 작업이 수행된 결과를 나중에 Callback함수를 통해 알려주는 구조가 있다. 하지만 Callback을 너무 많이 사용하게 되면 'Callback 지옥' 에서 헤어나올 수 없다. 이러한 문제를 해결하기 위해 Promise를 사용한다.

// 비동기
function Func1() {
  setTimeout(() => {
    console.log(1);
  }, 2000);
}

function Func2() {
  setTimeout(() => {
    console.log(2);
  }, 1000);
}

f1();
f2();

// 결과
// 2
// 1


// 비동기 => 동기
setTimeout(() => {
  console.log(1);
  setTimeout(() => {
    console.log(2);
    setTimeout(() => {
      console.log(3);
    }, 1000);
  }, 2000);
}, 1000);

// 결과
// 1
// 2
// 3

Promise 방식

new Promise((resolve , reject) => {
  console.log(1);
  resolve();
})

.then(() => {
  console.log('1 Complete');
  
})
.catch(() => {
  console.log('1 Error');
})

// 결과
// 1
// 1 Complete


new Promise((resolve , reject) => {
 console.log(1);
  resolve();
})
.then (() => {
  console.log(2);
  
  new Promise((resolve , reject) => {
    console.log(3);
    resolve();
  })
  .then(() => {
    console.log(4);
  })
})

// 결과
// 1
// 2
// 3
// 4

Promise 함수가 성공적으로 처리가 되면 resoleve 호출하여 then으로 분기가 가능하다.
비정상적으로 처리가 되면 reject를 호출하여 catch로 분기하여 에러를 출력한다.

then 안에 Promise 함수를 계속 생성하면, 다시 Callback지옥에서 헤어나올 수 없기 때문에
내부에서 실행되는 Promise를 return 시켜주어 내부 Scope에서 나오기 때문에 내부에 있던 Promise 함수의 then을 외부에서 호출 할 수 있게 된다.

new Promise((resolve , reject) => {
 console.log(1);
  resolve();
})
.then (() => {
  console.log(2);
  
  return new Promise((resolve , reject) => {
    console.log(3);
    resolve();
  })
})
  .then(() => {
    console.log(4);
  
  return new Promise((resolve , reject) => {
    console.log(5);
    resolve();
  })
})
.then(() => {
  console.log(6);
})

0개의 댓글