JavaScript 응용3 복습

Udemy - 한입크기로 잘라 먹는 리액트

자바스크립트 동작원리 동기방식 비동기방식 싱글스레드 Call Stack Heap WebAPIs Event Loop Callback Queue 콜백지옥



JavaScript 응용3

📌 동기 & 비동기

✔️ 핵심 Point

📝 자바스크립트의 싱글 스레드 작업 수행 방식

  • 코드를 실행시키기 위한 연산 과정 이해
  • 연산 과정은 코드 한줄 한줄마다 수행한다고 생각하면 됨.
    이 때 이 연산과정을 수행하는 친구를 Thread(스레드) = 일꾼이라고 보면 됨
    스레드는 코드를 한줄한줄 수행해주는 일꾼이다.
  • 이 스레드 한 개로 우리가 지시하는 taskA를 수행하고 그다음 taskB, taskC를 수행
  • 이 스레드는 taskA를 실행하고 있을 때 혼신의 힘을 다하고 있기 때문에 taskB, taskC를 같이 수행하지 않음
  • taskA가 끝나면 그때야 비로소 taskB가 실행되고 taskB가 끝나면 taskC가 수행됨
  • 📝 동기 방식 : 이렇게 지시한 순서대로 작업하면서 앞에 작업이 끝날 때까지 기다렸다가 바로 뒤에 작업이 실행되는 방식
  • 📝 블로킹 방식 : 어떤 작업이 수행될 때 다른 작업이 수행될 수 없는 방식
  • 우리가 작성한 모든 코드들은 이 스레드라는 녀석이 동기적 방식으로 처리해주고 있던 것.
    그런데 이런 방식은 짧은 시간 안에 이루어지므로 그다지 문제되지는 않아보이는데..

📝 동기 처리 방식의 문제점

  • 동기처리 방식의 경우, 무거운 작업이 걸려있다면 그럴 때 문제가 될 수 있음
  • tasKA는 0.3초, taskB는 20초, taskC는 10초로 모든 작업이 종료되려면 30초나 걸리게 된다면?
  • 우리가 자바스크립트를 사용하는 이유는 웹사이트의 동작을 제어하고, 사용자가 버튼을 누르거나 스크롤을 내리거나 대화를 할 경우 등등 동적 반응을 만들기 위해서인데 만약 사용자가 버튼 하나 누르려고 30초나 기다려야 한다면?
  • 동기 방식의 경우 하나의 작업이 너무 오래 걸리게 되면 모든 작업이 오래 걸려 하나의 작업이 종료되기 전까지 올스탑되어 전반적인 흐름이 느려짐. (성능성의 문제)

이건 우리의 일을 수행하는 일꾼인 스레드가 1명 밖에 없어서 그런게 아닌가요?

📝 멀티 스레드

  • 코드를 실행하는 일꾼인 스레드를 여러 명으로 늘려본다면?
  • 멀티 스레드 : 오래 걸리는 일이 있어도 다른 일꾼에게 지시하면 되므로 괜찮음
  • Thread에게는 0.3초 걸리는 taskA를 할당, ThreadA에게는 20초가 걸리는 taskB를 할당, ThreadB에게는 10초가 걸리는 taskC를 할당해준다.
  • 이렇게 오래 걸릴 작업을 분할해서 결과값을 모으면 해결은 할 수 있게 되지만...

📝 안타깝게도 자바스크립트는 일꾼이 한명

  • 자바스크립트는 싱글 스레드 방식으로 작동함.
  • 즉, 위와 같이 멀티 스레드 방식으로 일꾼을 여러 명 늘리는 방식으로 사용이 불가능

📝 싱글 스레드의 비동기 방식

  • 자바스크립트는 일꾼(스레드)가 비록 하나일지라도 여러 개를 동시에 실행시켜버리면 됨
  • taskA, taskB, taskC 작업을 끝나던 말던 관심없이 동시에 다 실행시켜버림
  • 비동기 방식 : 이렇게 동기 방식과 다르게 여러 개의 작업을 동시에 수행하는 방식
  • 논블로킹 방식 : 이렇게 하나의 작업이 스레드를 점유하지 않고 동시에 진행되는 방식
  • 그런데 이렇게 동시에 다 실행시켜버리면 이 작업들이 정상적으로 끝나는지는 어떻게 확인하고, 작업들의 결과는 어떻게 확인할 수 있을까? => 콜백함수

📝 콜백함수 활용

  • 비동기적으로 실행된 각각의 함수들에게 작업이 끝나면 실행할 콜백함수를 붙여서 전달해주면 됨
  • taskA에 resultA를 매개변수로 받아서 "A가 끝났습니다. 작업 결과 ${resultA}" 이런 식의 콜백함수를 붙여서 전달. taskA는 자기가 할 일을 다 하고 나서 콜백함수로 전달된 이 함수를 호출하여 이 결과값을 이용할 수 있도록 만들어 줌
  • 이렇게 비동기방식을 이용해서 함수를 호출할 때 콜백함수를 붙이면 콜백함수는 그 비동기방식의 결과값이나 어떻게 잘 끝났는지를 확인하는 역할을 하게 됨
  • 자바스크립트로 각각의 다른 작업을 하는 3가지 함수를 만들고 실행하면서 비동기 방식 이해해보기

📝 동기 방식

function taskA(){
	console.log("A 작업 끝");
}

taskA(); // A 작업 끝
console.log("코드 끝"); // 코드 끝

// 예상했던 것처럼 A 작업 끝이 나온 후 코드 끝이 실행

📝 비동기 방식

// setTimeout (타이머를 만들 수 있는 내장 비동기 함수, 2개의 파라미터가 들어감)
// 처음에는 콜백함수를 넣어주고 그 다음에는 delay time(ms초단위)을 넣어준다.

function taskA(){
	setTimeout( () => {
    	console.log("A TASK END")
    }, 2000)
}

taskA();
console.log("코드 끝"); 

// 코드 끝이 먼저 실행 후 A TASK END
// 코드 상으로는 taskA가 먼저지만, 이것을 기다리지 않고
// console.log가 먼저 실행된 후 A TASK END가 실행됨

📝 2개에 파라미터를 받아서 그 2개를 더한 값을 3초 뒤에 전달하기

function taskA(a, b, cb){ // res를 실행할 수 있게 콜백이라는 의미에서 cb로 명명하고 전달
	setTimeout( () => {
    	const res = a + b; // a와 b를 더한 값을 res라는 지역상수에 저장(이 안에서만 유용한)
        cb(res); // 이렇게 출력
    }, 3000)
}

taskA(3, 4, (res) => {
	console.log("A TASK RESULT :", res)}
    // res 지역상수를 바깥에서 사용할 수 있게 콜백함수 사용
    // 결과값을 파라미터로 받아서
    // 이쪽으로 코드의 흐름을 넘겨서 res의 값을 전달받아 정상적으로 출력하도록 해줌
);
console.log("코드 끝"); 

// 코드 끝이 먼저 실행된 후 파라미터를 더한 결과값인 7이 실행

📝 taskB 함수를 만들어서 1초 뒤에 전달 받은 파라미터에 *2 해주기

function taskA(a, b, cb) {
	setTimeout( () => {
    	const res = a + b;
        cb(res);
    }, 3000);
}

function taskB(a, cb){
	setTimeout( () => {
    	const res = a * 2;
        cb(res);
    },1000);
}

taskA(3, 4, (res) => {
	console.log("A RESULT : ", res);
});

taskB(7, (res)=>{
	console.log(B RESULT : ", res);
});

console.log("코드 끝");

// 코드 끝이 실행된 후
// B TASK RESULT : 14가 실행되고 (1초 뒤)
// 마지막으로 A TASK RESULT : 7이 실행됨 (3초 뒤)

// 먼저 호출되었어도 이렇게 차이가 나는 것은 taskB는 1초 기다리고,
// taskA는 3초 기다리기 때문에 그래서 taskB가 먼저 끝남

📝 2초 뒤에 전달 받은 파라미터에 * -1 하는 taskC 함수 추가하기

function taskA(a, b, cb) {
	setTimeout( () => {
    	const res = a + b;
        cb(res);
    }, 3000);
}

function taskB(a, cb){
	setTimeout( () => {
    	const res = a * 2;
        cb(res);
    },1000);
}

function taskC(a, cb){
	setTimeout( () => {
    	const res = a * -1;
        cb(res);
    },2000);
}

taskA(3, 4, (res) => {
	console.log("A RESULT : ", res);
});

taskB(7, (res)=>{
	console.log(B RESULT : ", res);
});

taskC(14, (res)=>{
	console.log(C RESULT : ", res);
});

console.log("코드 끝");

// 코드 끝이 실행되고
// 1초 뒤에 B TASK RESULT : 14가 나오고
// 2초 뒤에 C TASK RESULT : -14가 나오고
// 3초 뒤에 A TASK RESULT : 7이 나옴

📌 자바스크립트의 엔진은 어떻게 동기&비동기 코드를 구분해서 수행할까?

✔️ JS Engine은 메모리를 할당하는 Heap과 코드를 실행하는 Call Stack 으로 이루어져 있다.
✔️ Heap : 변수나 상수들에 사용되는 메모리를 저장하는 영역
✔️ Call Stack : 우리가 작성한 코드를 쌓아서 실행하는 영역

📝 동기방식 처리

✔️ 핵심 Point
프링글스통과 같다고 생각하기!
Call Stack에 가장 마지막에 들어온 것부터 먼저 제거되는 거라고 이해하면 됨

  • 자바스크립트의 코드가 실행되면 Call Stack에 최상위 문맥인 Main Context가 들어옴
  • Call Stack에 Main Context가 들어오는 순간이 프로그램의 실행 순간이고, 이 Main Context가 Call Stack에서 나가는 순간이 프로그램의 종료 순간이다.
  • 그런데 one함수와 two함수는 생성만 하고 넘어가므로 실제 수행되는 부분은 console.log(three()); 이 라인이 실행하게 되므로 three()가 Call Stack에 들어간다.
  • three라는 함수에서는 two함수를 호출하고 있고, two함수를 실행하려고 보니 one함수를 호출하고 있다. Call Stack에는 two()가 쌓이고, one()도 쌓임
  • one함수를 실행하러 들어갔더니 1을 return하고 바로 종료되는 것을 볼 수 있음.
  • 가장 마지막에 들어온 one함수가 Call Stack에서 1을 반환하고 먼저 제거됨
  • 그 다음은 two함수가 one함수의 결과값(1)에 +1을 더해 2를 반환하고 제거됨
  • 그 다음으로 three함수에 two함수의 결과값(2)에 +1을 더해 3을 반환하고 제거됨
  • 그 다음으로 console.log로 three함수의 결과값인 3이 console에 출력되고 Call Stack에서 제거됨
  • console.log까지 끝났으니 더이상 수행할 코드가 없어서 Main Context까지 제거되어 프로그램은 종료
  • 아까 프로그램을 수행하는 녀석을 스레드라고 했는데 스레드는 이 Call Stack 하나만을 담당함. 이 스레드는 Call Stack의 작동방식대로 명령을 처리한다고 보면 되는데, 그래서 자바스크립트가 싱글 스레드라고 생각하면 됨

📝 비동기방식 처리

  • setTimeout 함수를 포함해서 asyncAdd 함수는 비동기 작업을 실행하고 있음
  • 그런데 오른쪽처럼 기존의 JS Engine(Heap, Call Stack) 외에 Web APIsCallback Queue, Event Loop라는 새로운 구성요소들이 생겨났음
  • 이 JS Engine 외에 3가지 구성요소들은 JS Engine과 웹브라우저의 상호작용을 위해서 존재하는데, 그 중 가장 대표적인 상호작용이 바로 비동기방식 처리
  • Main Context가 먼저 Call Stack에 들어가서 프로그램이 실행됨
  • asyncAdd() 함수의 호출부가 다음으로 Call Stack에 들어가고, 3개의 파라미터인 1, 3, 콜백함수를 전달.
  • 그 다음으로 asyncAdd() 함수를 실행하려고 들어가 보니 setTimeout이라는 비동기 함수를 호출하고 있음. 그런데 이 setTimeout 비동기 함수 안에는 cb라고 하는 콜백함수도 포함하고 있음.
  • 그래서 Call Stack에는 cb()가 포함된 setTimeout() 함수가 추가됨
  • 그런데 setTimeout을 빨간색으로 표시한 이유는 비동기함수이기 때문에 : 3초를 기다리게 되는 비동기함수가 있을 경우 JS Engine은 Web APIs로 넘겨버림. 그러면 Web APIs로 넘겨진 cb()가 포함된 setTimeout()는 실행이 멈추는 게 아니라 3초를 기다리게 됨. 이렇게 setTimeout() 함수가 Web APIs에 머무르게 되어 바로 다음 코드를 수행할 수 있게 되어 asyncAdd() 함수를 끝낼 수가 있게 되는 것임.
  • 그래서 asyncAdd() 함수를 실행을 마쳤기 때문에 Call Stack에서 제거됨
  • asyncAdd() 함수가 실행 종료 후 Web APIs에 있던 cb()가 포함된 setTimeout() 함수의 3초의 기다림이 끝난 이런 상황에서는 setTimeout() 함수는 제거되고, 포함되있던 cb() 함수는 Web APIs에서 Callback Queue로 옮겨지게 됨 (왜냐면 이제 수행을 해야하기 때문)
  • 이렇게 Callback Quere로 옮겨지게 되면 가운데에 있는 Event Loop에 의해서 다시 Call Stack으로 옮겨질 수가 있는데 cb()함수가 Web APIs에 있다가 Callback Queue로 이동 후 Event Loop에 의해 Call Stack에 들어간다는 의미는 이제 실제로 실행이 이루어진다는 것이라고 보면 됨
  • 이 때 Event Loop는 cb()를 Call Stack으로 넘겨주기 위해 Call Stack에 Main Context를 제외한 다른 코드가 모두 실행/종료되었는지 계속적으로 확인함. 만약 아무것도 남아있지 않게 될 때 cb()함수가 수행할수 있게 되므로 Call Stack에 넘겨줌.
  • cb() 함수가 Call Stack에 넘겨져서 결과:4를 출력하게 된 후 수행이 종료되어 Call Stack에서 제거됨. 그 후 Main Context까지 제거되면서 비동기방식을 포함한 프로그램 수행은 종료됨

✔️ 이것을 자세하게 다룬 이유?!
앞으로 비동기방식의 코드를 작성하게 될 일이 많기 때문에 JS Engine이 비동기방식을 어떻게 처리하는지 알고 있으면 동기방식의 코드와 비동기방식의 코드를 같이 사용했을 때, 이래서 이런 문제가 발생했고, 그러면 이렇게 바꾸면 되겠구나를 떠올리기가 쉬워지기 때문.

📝 예제

// 위의 코드들을 활용, 호출부를 바꿔서 다시 비동기방식처리 이해

function taskA(a, b, cb) {
	setTimeout( () => {
    	const res = a + b;
        cb(res);
    }, 3000);
}

function taskB(a, cb){
	setTimeout( () => {
    	const res = a * 2;
        cb(res);
    },1000);
}

function taskC(a, cb){
	setTimeout( () => {
    	const res = a * -1;
        cb(res);
    },2000);
}

taskA(4, 5, (a_res) => {
    // 4와 5를 매개변수로 받아 더한 결과값을 taskB의 인자로 넣기
    // taskB에는 a_res, b_res 콜백
    // taskC에는 b_res, c_res 콜백
    
    console.log("A RESULT :", a_res);
    taskB(a_res, (b_res) => {
       console.log("B RESULT :", b_res);
       taskC(b_res, (c_res) => {
          console.log("C RESULT :", c_res);
          
      });
   });
});


console.log("코드 끝");


// 그러면 코드 끝이 가장 먼저 실행 되고
// 3초를 기다렸다가 A RESULT : 9가 나옴
// taskA가 끝난 다음 콜백에서 taskB를 호출하여 taskA의 결과값인 9를 넣어서 호출하면
// 전달받은 인자인 9 * 2를 하여 B RESULT : 18이 나오고
// 18을 taskC의 값으로 전달하여 * -1을 하여 C RESULT : -18이 나오는 것을 확인할 수 있음

📝 콜백지옥

✔️ 만약 위와 같은 task가 3개가 아니라 20,30개나 된다면
이렇게 콜백함수가 끝도 없이 이어지게 되는 경우를 만날 수 밖에 없음.
✔️ 비동기처리의 결과를 또 다른 비동기처리의 결과로 이용하는 로직이 계속 길어지게 되면서
콜백함수가 안쪽으로 파고드는 이런 현상을 콜백지옥이라고 함 (Callback Hell)
✔️ 이런 콜백 지옥을 구원하기 위한 자바스크립트의 비동기 담당 객체인 Promise가 있음


복습ing

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글