동기와 비동기, Callback/Promise와 JS의 멀티태스킹

Seongmin·2022년 11월 8일
0

CS

목록 보기
3/6

동기와 비동기

동기 : 어떤 요청이 왔을 때, 그 요청이 완료가 되어야 다음 요청을 실행하는 방식
비동기 : 어떤 요청이 왔을 때, 그 요청이 완료되기 전에도 다음 요청을 실행할 수 있는 방식

다음 코드들을 살펴보자.

동기

function func1(){
  console.log(1);
  func2();
}

function func2(){
  console.log(2);
  func3();
}

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

func1();
1
2
3

동기는 요청을 보낸 후 응답(결과물)을 받아야만 다음 동작이 이루어진다. 즉, 순차적으로 진행되며 어떤 작업이 수행 중이라면 다음 작업은 대기하게 된다.

비동기

function func1() {
	console.log('1');
	func2();
}
function func2() {
	setTimeout(function() {
		console.log('2');
 }, 1000);
	func3();
 }
function func3() {
	console.log('3');
}

func1();
1
3
2

setTimeout 함수는 대표적인 비동기 함수로, 순차적인 흐름을 보장하지 않는다. 때문에 func1이 실행된 후 func2에서 setTimeout을 호출하자마자 바로 func3을 실행하고, 1초가 지난 후 setTimeout 안의 구문을 실행한다.

FileI/O, Timer, NetworkI/O 등 CPU가 IDLE상태에 있는 함수들의 경우들이 비동기 함수이다.

Callback

따라서 비동기 처리를 마치 동기처럼 처리하기 위해 콜백함수가 필요하다.

다음과 같은 코드가 있다고 하자.

function func1(func) {
	console.log('1');
	func2(func);
}
function func2(callback) {
	setTimeout(function() {
		console.log('2');
      	callback();
	}, 1000);
}

function func3() {
	console.log('3');
}

func1(func3);
1
2
3

2를 출력하고 나서 3을 출력하기 위해, callback 함수를 인자로 받고 setTimeout 함수 내에 포함시켰다.
이로써 문제를 해결했다고 생각하지만, 여러 함수들을 순차적으로 실행시키고자 할 때, 콜백함수 안에 콜백함수를 여러번 중첩시키면 콜백지옥에 빠질 수 있다.

step1(function (value1) {
    step2(function (value2) {
        step3(function (value3) {
            step4(function (value4) {
                step5(function (value5) {
                    step6(function (value6) {
                        // Do something with value6
                    });
                });
            });
        });
    });
});

Promise

콜백함수의 문제점을 보완하기 위해, 비동기 함수를 동기적으로 처리하는 또다른 객체 Promise가 있다. Promise는 비동기 작업이 완료된 이후에 다음 작업을 연결시켜 진행할 수 있는 기능을 제공한다. 작업 결과에 따라 성공 또는 실패를 리턴하며 결과 값을 전달받을 수 있다.

promise에 대해 쉽게 설명된 예제가 있다.

출처 : https://sangminem.tistory.com/284

function goToSchool() {
    console.log("학교에 갑니다.");
}

function arriveAtSchool_tobe_adv() {
    return new Promise(function(resolve, reject){
        setTimeout(function() {
            var status = Math.floor(Math.random()*2);
            if(status === 1) {
                resolve("학교에 도착했습니다.");
            } else {
                reject("중간에 넘어져 다쳤습니다.");
            }
        }, 1000);
    });
}

function cure() {
    console.log("양호실에 가서 약을 발랐습니다.");
}

function study() {
    console.log("열심히 공부를 합니다.");
}

위 함수들을 실행시키기 위해 다음과 같이 입력해보자.

goToSchool();
arriveAtSchool_tobe_adv()
.then(function(res){
    console.log(res);
    study();
})
.catch(function(err){
    console.log(err);
    cure();
});
학교에 갑니다.
Promise {<pending>}
학교에 도착했습니다.
열심히 공부를 합니다.
학교에 갑니다.
Promise {<pending>}
중간에 넘어져 다쳤습니다.
양호실에 가서 약을 발랐습니다.

promise 객체의 resolve, reject 함수의 인자로 들어간 값들은 각각 then, catch 인자로 들어가는 함수의 인자이다. then에 인자가 두 개가 들어가면, 첫 번째 인자 함수는 resolve일 때, 두 번째 인자 함수는 reject일 때 실행된다. finally 또한 chaining하여 쓸 수 있다.
promise는 체이닝 기법을 이용하여 콜백 지옥에서 탈출할 수 있다.

async / await

function 앞에 async를 붙여주면 promise 객체가 반환된다. Promise가 아닌 값을 리턴하더라도 내부적으로 Promise로 감싸서 리턴한다.

다음 코드를 살펴보자.
출처 : https://sangminem.tistory.com/479

function greet() {
    return new Promise(function(resolve){
        setTimeout(function() {
            resolve('hello');
        }, 1000);
    });
}

(async function() {
    var result = await greet(); //resolved 될 때까지 대기
    console.log(result);
})();

이는 비동기 작업의 순차 처리가 일반 순차 프로그래밍과 동일하게 가능하다.

Multitasking in JS

JS는 single thread로 프로그램이 동작한다. 하지만 비동기방식은 멀티태스킹으로 진행된다. JS에서 어떻게 비동기 태스크를 처리할 수 있을까?

Call stack

JS는 single thread 프로그래밍 언어이기 때문에 한 번에 하나의 일만 할 수 있다. 이는 call stack이 하나이기 때문인데, 다음과 같은 코드가 있다고 하자.

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

위 코드는 아래 사진과 같은 과정을 거친다.

하지만, 다음과 같이 단일 call stack에서 함수를 수행하다가 수행시간이 긴 함수를 만났을 때, 브라우저는 아무것도 할 수 없는 blocking이 일어난다.

이를 해결하기 위해 Web API에서 비동기식 처리를 해준다.

Web API

Web API는 JS 엔진이 아니라 browser에서 제공하는 API이다. Call stack에 쌓인 함수 중 비동기함수(setTimeout, DOM, Ajax...)는 Web API에서 실행한다.

예를 들어 SetTimeOut(Function(){}, 5000) 으로 5초짜리 비동기 함수가 있다면 
Web APIs에서 5초짜리 타이머가 생성된다.
5초를 이곳에서 머루른 이후 Web APIs는 이 Run함수를 Callback Queue으로 이동시킨다.

자바스크립트는 웹 브라우저나 Node.js의 자바스크립트 엔진에서 실행이 된다. 이 엔진에는 자바스크립트를 돌리는 하나의 스레드가 존재한다. 또한 이 엔진 뿐만이 아니라 비동기식 처리 모델인 Web API라는 것이 함께 동작하면서 여기에서 setTimeout 이나 AJAX로 http 데이터를 가져오는 시간이 소요되는 일들을 처리한다.

이 Web API들이 자바스크립트 엔진 스레드와는 따로 비동기 처리를 따로 돌면서 콜백함수를 가지고 이벤트 루프에 들어가 처리되는대로 콜백함수를 다시 자바스크립트 엔진으로 돌려보내준다.

Callback Queue(Event Queue, Task Queue)

Web API 에서 보내진 비동기 처리가 callback에 의해 queue에 enqueue된다.

여기 모인 비동기함수는 이벤트 루프의 감시하에 놓이게 된다.

Event Loop

Event Loop는 Call Stack과 Queue를 감시하다가, Call Stack이 비면 Queue에 쌓인 비동기 함수를 Call stack으로 보내주는 역할을 한다. Call Stack이 비어있을 때 Callback Queue에 쌓여있는 비동기 함수 중 가장 먼저 들어온(선입선출) 함수를 보내준다.

출처


https://lunuloopp.tistory.com/entry/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%98-%EC%9D%B4%ED%95%B4-callback%ED%95%A8%EC%88%98-promise-async-await
https://blog.metafor.kr/164
https://sangminem.tistory.com/275
https://librewiki.net/wiki/%EC%BD%9C%EB%B0%B1_%EC%A7%80%EC%98%A5
https://sangminem.tistory.com/284
https://velog.io/@xedni/JavaScript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%91%EB%8F%99-%EC%9B%90%EB%A6%ACEvent-Loop%EC%99%80-Call-Stack-Web-API-Callback-Queue

0개의 댓글