모래블로그

[JavaScript] 동기, 비동기 본문

Language/JavaScript

[JavaScript] 동기, 비동기

별모래 2024. 2. 15. 17:11
728x90

1. 자바스크립트의 동기(Synchronous)

자바스크립트는 싱글 스레드 언어이기 때문에 한 번에 하나의 작업만 수행할 수 있다. 

즉, 이전 작업이 완료되어야 다음 작업을 수행하는 순차적인 방식으로 진행이 되는데, 이러한 방식을 동기(Synchronous) 고 부른다.

 

예를 들어, 이렇게 입력했을 때

결과가 "one", "two", "three" 가 차례대로 출력되는 것을 확인할 수 있다.

 

그렇다면 왜 이렇게 나올까 ?

2. 자바스크립트 엔진의 동작 원리

자바스크립트는 콜 스택(Call Stack) 메모리 힙(Memory Heap)이라는 메모리 구조를 통해 데이터 및 코드 실행을 관리한다.

 

💡엔진의 주요 구성 요소

1) Memory Heap : 메모리 할당이 일어나는 곳

객체, 배열, 함수 등 참조타입 데이터가 메모리 힙에 할당되며, 이 메모리는 자동으로 관리하고 사용하지 않는 메모리를 자동으로 해제한다.

 

2) Call Stack : 코드 실행에 따라 스택이 하나씩 쌓이는 곳

숫자 등 원시 타입 데이터가 저장된다.

자바스크립트에서 함수를 호출하면 Call Stack이라는 곳에 호출 순서대로 차곡차곡 쌓이고,

Stack은 맨 마지막에 호출된 함수를 맨 먼저 반환한다.

LIFO 구조(Last In First Out) 이다.

 

3.  비동기(Asynchronous)

동기 방식은 간단하고 직관적이지만, 작업이 오래 걸리거나 응답이 늦어지는 경우에는 전체적인 성능과 사용자 경험에 영향을 줄 수 있다.

예를 들어 서버에 데이터를 요청하고 응답을 받아야 하는 작업이 있다면, 응답이 올 때까지 다른 작업을 하지 못하고 대기해야 한다. 프로그램의 흐름이 멈추거나 지연되는 문제가 발생할 수 있다. 

 

따라서 자바스크립트에서 여러 작업을 동시에 처리하기 위해 비동기(Asynchronous)라는 개념을 도입하여, 특정 작업의 완료를 기다리지 않고 다른 작업을 동시에 수행할 수 있도록 하였다.

 

비동기는 어떠한 요청을 보내면 그 요청이 끝날 때까지 기다리는 것이 아니라, 응답에 관계 없이 바로 다음 동작이 실행되는 방식을 말한다.

 

 메인 스레드가 작업을 다른 곳에 인가하여 처리되게 하고, 그 작업이 완료되면 콜백 함수를 받아 실행하는 방식으로, 쉽게 말해 작업을 백그라운드에 요청하여 처리되게 하여 멀티로 작업을 동시에 처리하는 것으로 보면 된다. 

 

서버에 데이터를 요청하고 응답을 받아야 하는 작업이 있다면, 응답이 오는 것과 상관없이 다른 작업을 계속 이어나가 병렬로 작업을 동시 처리가 가능해져 프로그램의 흐름이 멈추거나 지연되지 않게 된다. 따라서 Task들이 병렬적으로 동시에 처리되게 되고 총 코드 실행 시간은 획기적으로 줄어들게 된다.

 


4. 비동기 처리의 문제점

비동기는 요청한 작업의 완료 여부를 기다리지 않고 자신의 그 다음 작업을 계속 수행해 나가기 때문에,

만약 그 다음 실행할 작업이 이전에 요청한 작업의 결과가 반드시 필요한 경우 문제가 발생한다.

 

예를 들어, 서버의 데이터베이스를 조회하여 데이터를 가져오는 로직이 있다.

getDB() 함수를 통해 DB를 조회하는데, 3초가 걸린다고 가정한다. 그리고 DB로부터 응답을 받게 되면 data 변수에 저장하고 값을 두 배 곱셈 연산 후 출력하려고 한다.

function getDB() {
    let data;
    // 데이터베이스에서 값을 가져오는데 3초 걸린다고 가정 (비동기 처리)
    setTimeout(() => {
        data = 100;
    }, 3000);

    return data;
}

function main() {
    let result = getDB();
    result *= 2;
    console.log('result : ', result);
}

main();

 

 

결과는 NaN 이라는 값으로 출력이 된다.

그 이유는 비동기 함수인 setTimeout 함수가 3초 동안 대기하는 동안 완료될 때까지 기다리지 않고 다음 코드인 console.log(result) 를 실행하였기 때문이다. 이 때, result 변수에 아직 데이터가 저장되지 않았기 때문에 여기에 연산을 하니 이상한 값이 출력된 것이다.

 

위의 예제처럼 순서를 맞춰야하는 경우, 동기로 처리해야 되나 싶지만 이를 해결하는 몇가지 기법이 있다.

가장 대표적인 것이 콜백 함수 기법이다.

 


5.  비동기를 알맞게 처리하기 위한 기법

5-1. 비동기적 Callback

 

비동기를 다룰 때 자주 엮여 등장하는 개념이 콜백(callback) 함수이다. 

콜백 함수는 자바스크립트의 일급 객체 특성을 이용해 함수의 매개변수에 함수 자체를 넘겨, 함수 내에서 매개변수 함수를 실행하는 기법을 말한다.

 

function getDB(callback) {
    // 데이터베이스로부터 3초 후에 데이터 값을 받아온 후, 콜백 함수 호출
    setTimeout(() => {
        const value = 100;
        callback(value);
    }, 3000);
}

function main() {
    // 호출할 작업에 콜백 함수를 넘김
    getDB(function(value) {
        let result = value * 2;
        console.log('result : ', result);
    });
}

main();

 

위 코드는 콜백 함수 내에서 result 변수의 값을 받아 출력하므로, 비동기 작업이 완료된 후에 출력된다.

즉, 콜백 함수는 비동기 함수에서 작업 결과를 전달받아 처리하는데 사용되어 작업 순서를 맞출 수 있게 되는 것이다.

따라서 비동기 함수와 콜백 함수는 서로 밀접한 관계를 가지고 있다.

 

다만 너무 복잡하게 얽힌 비동기 처리 때문에 콜백 함수 방식은 코드 복잡도를 증가시켜 개발자가 어플리케이션의 흐름을 읽기 어려워지는 등의 문제가 있을 수 있어 잘못하면 콜백 지옥(callback hell) 에 빠질 수 있다는 단점이 있다. 

콜백 지옥(callback hell)이란 콜백 함수를 익명 함수로 전달하는 과정에서 또 다시 콜백 안에 함수 호출이 반복되어 코드의 들여쓰기 순준이 감당하기 힘들 정도로 깊어지는 현상

 

 

5-2. Promise

콜백 함수는 정식으로 지원하는 비동기 전용 함수가 아니다.

자바스크립트의 Promise 객체는 이러한 콜백함수의 한계점을 극복하기 위해 비동기 처리를 위한 전용 객체로서 탄생하였다.

Promise는 비동기 작업 성공 또는 실패와 그 결과 값을 나타내는 객체이다.

결과 값을 돌려 받을 수 있으므로 이후 처리를 컨트롤 할 수 있게 된다. 

또한, Promise 를 사용하면, 한 블록 내 많은 중첩 함수를 쓰지 않고도 비동기 처리가 가능하다.

function getDB() {
  return new Promise((resolve) => {
    setTimeout(() => {
      const value = 100;
      resolve(value);
    }, 3000);
  });
}

function main() {
  getDB()
    .then((value) => {
      let result = value * 2;
      console.log('result : ', result);
    })
    .catch((error) => {
      console.error(error);
    });
}

main();

 

resolve(value) : 작업이 성공적으로 끝난 경우 

👉프로미스를 실행한 곳의 then 으로 들어간다.

reject(error) : 작업이 성공적이지 않은 경우(에러 발생 시)

👉프로미스를 실행한 곳의 catch로 들어간다.

 

5-3. async / await

하지만 Promise 도 완벽한 해결책이 아니다. 왜냐하면 Callback Hell 이 있듯이 지나친 then 핸들러 함수의 남용으로 인한 Promise Hell 이 존재하기 때문이다. 즉, 프로미스가 여러 개 연결되면 코드가 길어지고 복잡해질 수 있다는 것이다.

그래서 자바스크립트에는 async / await 라는 문법이 또한 추가되었다.

async/ await도 프로미스를 기반으로 하지만, Promise 로직을 더 쉽고 간결하게 사용할 수 있게 해준다.

자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다.(ES2017)

 

사용법

  • 함수앞에 async를 붙이면 해당 함수는 자동으로 프로미스를 반환하게 된다.
  • 비동기로 처리되는 부분에 await를 붙이면 해당 프로미스가 끝날 때까지 기다린다. (동기적으로 처리)
  • await은 async가 붙은 함수 안에서만 사용 가능하다.
function getDB() {
    return new Promise((resolve, reject) => {
        // 데이터베이스에서 값을 가져오는 3초 걸린다고 가정 (비동기 처리)
        setTimeout(() => {
            const value = 100;
            resolve(value); // Promise 객체 반환
        }, 3000);
    });
}

async function main() {
    let result = await getDB(); // await 키워드로 Promise가 완료될 때까지 기다림
    result *= 2;
    console.log('result : ', result);
}

main(); // 메인 스레드 실행

 

 

 

 

 

 


참조 

https://ljtaek2.tistory.com/142

https://inpa.tistory.com/entry/%F0%9F%8C%90-js-async

https://velog.io/@kirin/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%94%EC%A7%84JavaScript-engine

https://hanamon.kr/javascript-%EC%BD%9C%EB%B0%B1-%EC%A7%80%EC%98%A5-%ED%83%88%EC%B6%9C%ED%95%98%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95/

https://velog.io/@jubby/%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%B0%A9%EC%8B%9D-Callback-Hell-Promise-Asyncawait

https://velog.io/@jiwon/Javascript%EB%8A%94-%EB%8F%99%EA%B8%B0%EC%9D%BC%EA%B9%8C-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%9D%BC%EA%B9%8C

https://yoo11052.tistory.com/165

 

728x90