Client Language/JavaScript

웹 애플리케이션의 꽃 - 비동기(asynchronous) 쉽게 이해하기

MingyuKim 2023. 11. 20.

서론

여태껏 당연하게 사용해 왔던 '비동기' 혹은 '비동기 통신'. 문득 스스로 '비동기가 뭐야?'라고 질문했다. 명쾌히 대답이 나오지 않았다. 부끄러움은 뒤로 한 채, 그 의미를 다시 한번 알아보고 여태껏 사용해 왔던 비동기 방식에 대해 학습해 보기로 한다.

웹 애플리케이션의 꽃 - 비동기(asynchronous) 쉽게 이해하기 - 서론

 

 

비동기(Asynchronous) vs 동기(Synchronous)

비동기(asynchronous)는 컴퓨터 프로그래밍에서 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 실행하는 방식이다. 즉, 작업의 완료와 상관없이 프로그램의 흐름이 계속 진행되는 것을 뜻한다. 이는 동기(synchronous) 방식과 대비되는 개념이다.

 

동기(Synchronous) :

  • 동기 방식에서는 한 작업이 완료될 때까지 기다렸다가 그 다음 작업을 실행한다.
  • 즉, 작업이 순차적으로 이루어 진다는 것이다.
  • 예를 들어서, 데이터베이스에서 데이터를 조회한 후 결과를 응답받을 때까지 다음 코드로 넘어가지 않는 것이 동기 방식이다.

비동기 (Asynchronous) :

  • 비동기 방식에서는 한 작업을 시작하고 완료되기를 기다리지 않고 바로 다음 코드를 실행한다.
  • 이러한 비동기 작업은 종종 콜백(call back) 함수, 프로미스 (Promise), async/await 등을 사용하여 구현한다.
  • 예를 들어서, 웹 API로부터 데이터를 가져오는 동안 사용자 인터페이스가 멈추지 않고 다른 작업을 계속할 수 있는 것이 비동기 방식의 대표적인 예이다.

 

 

비동기 방식의 중요성

비동기(Asynchronous)는 웹 어플리케이션에서 매우 중요한 개념이다. 웹 어플리케이션은 네트워크 요청, 사용자 이벤트 처리 등 다양한 작업을 동시에 처리해야 하며, 이들 작업이 서로를 차단하지 않아야 한다.

 

예를 들어서, 서버로부터 데이터를 받아오는 동안에도 사용자는 웹 페이지 내부에서 스크롤을 하고, 버튼을 클릭한다. 이때 모든 네트워크 요청이 동기적으로 처리된다면, 데이터를 받아오는 동안 페이지가 멈추게 되어서 사용자는 어떠한 액션도 취할 수 없게 되어 사용자 경험 (UX)가 크게 저하될 것이다.

 

비동기 프로그래밍은 복잡할 수 있지만, JavaScript의 'Promise' 및 'asnyc', 'await' 과 같은 기능을 사용하면 비교적 쉽게 처리할 수 있다. 이러한 기능들은 비동기 작업의 결과를 효과적으로 관리하고, 코드를 더 읽기 쉽고 이해하기 쉬운 형태로 작성할 수 있도록 도와준다.

 

 

async/await 그리고 Promise

'Promise'는 비동기 작업의 최종 완료(응답 성공 또는 에러 실패 등)와 그 결과값을 나타낸다. 간단히, 어떤 작업이 완료되기를 '약속'하고 그 작업이 끝난 후 결과를 반환한다.

let myPromise = new Promise((resolve, reject) => {
    // 비동기 작업을 수행한다.

    let condition; // 어떤 조건
    if (condition) {
        resolve('성공'); // 작업이 성공하면 resolve 호출
    } else {
        reject('실패'); // 작업이 실패하면 reject 호출
    }
});

myPromise
    .then((result) => {
        console.log(result); // '성공' 출력
    })
    .catch((error) => {
        console.log(error); // '실패' 출력
    });

 

'async'와 'await'은 'Promise'를 더 쉽게 사용할 수 있게 해주는 JavaScript의 문법이다. 'async' 키워드는 함수 앞에 사용하는데 이는 함수가 비동기적으로 작동하도록 만든다. 'await' 키워드는 'Promise'의 응답을 기다리게 한다.

async function myAsyncFunction() {
    try {
        let result = await myPromise; // Promise가 해결될 때까지 기다림
        console.log(result); // '성공' 출력
    } catch (error) {
        console.log(error); // 오류 발생 시 '실패' 출력
    }
}

myAsyncFunction();

 

 

 

Promise , async/await을 각각 사용하여 데이터 가져오기

예를 들어서, 웹 API에서 데이터를 가져오는 비동기 작업을 'fetch' API를 사용해서 수행한다고 가정한다.

 

Promise 사용 예제 : 비동기 작업을 대표하는 객체로, 해당 작업이 완료되면 결과를 반환하거나 오류를 발생시킨다.

function fetchData() {
    return fetch('https://api.example.com/data') // 데이터를 가져온다.
        .then(response => response.json()) // 응답을 JSON으로 변환
        .then(data => console.log(data)) // 데이터 출력
        .catch(error => console.error('Error fetching data:', error)); // 오류 처리
}

fetchData();

 

async/await 사용 예제 : 'Promise'를 사용하기 더 쉬운 방법으로, 코드를 동기적으로 작성하는 것처럼 보이게 해준다.

async function fetchDataAsync() {
    try {
        let response = await fetch('https://api.example.com/data');
        let data = await response.json();
        console.log(data);
    } catch (error) {
        console.error('Error fetching data:', error);
    }
}

fetchDataAsync();

 

'async'와 'await'은 'Promise' 기반 코드를 좀 더 읽기 쉽고 이해하기 쉬운 방식으로 작성할 수 있게 해준다. 하지만 내부적으로는 'Promise'를 기반으로 동작한다.

 

 

Fetch API란?

Fetch API는 웹 브라우저에서 HTTP 요청을 보내고 받기 위한 현대적인 JavaScript 인터페이스이다. 이 API를 사용하면 서버로부터 리소스를 가져오거나, 서버에 새로운 리소스를 생성할 수 있다. Fetch API는 기존에 많이 사용했던 XMLHttpRequest(XHR) 보다 간결하고 유연한 대안으로 널리 사용되고 있다.

 

Fetch API의 주요 특징으로는, 비동기적으로 동작하며 'Promise' 객체를 반환한다. 이를 통해 비동기 요청의 결과를 쉽게 처리할 수 있다. 그리고 Fetch API를 사용하여 다양한 HTTP 요청(GET,POST,PUT,DELETE 등)을 보낼 수 있으며, JSON, TEXT, Blob 등 다양한 형식의 응답을 처리할 수 있다는 점이다. 또한 사용자 정의 헤더를 추가하거나, 쿼리 매개변수를 조작할 수 있고 CORS(Croos-Origin Resource Sharing) 관련 설정을 다룰 수 있다.

 

GET 사용 예시 :

fetch('https://api.example.com/data')
    .then(response => response.json()) // 응답을 JSON 형태로 파싱
    .then(data => console.log(data)) // 데이터 처리
    .catch(error => console.error('Error:', error)); // 오류 처리

 

POST 사용 예시 :

fetch('https://api.example.com/data', {
    method: 'POST', // HTTP 메서드 지정
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ key: 'value' }) // 보낼 데이터
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

 

XMLHttpRequest와 비교한다면, Fetch API는 XMLHttpRequest(이하 XHR)보다 더 간결하고, Promise 기반이라 비동기 처리가 더 편리하다. 또한 Fetch API는 스트림을 사용하여 요청과 응답을 처리할 수 있어, 대용량 데이터를 효율적으로 다룰 수 있고, 모든 최신 브라우저에서 Fetch API를 지원하기 때문에 지원 범위가 넓다.

 

Fetch API를 사용하여 스트림 처리를 하는 예제는 아래와 같다. 스트림(Stream)은 Fetch API에서 큰 데이터를 효율적으로 처리하는데 유용하게 사용된다. 스트림을 사용하면 데이터를 조각조각 나누어서 처리할 수 있게 되는데, 메모리를 절약하고 대용량 데이터를 더 효율적으로 다룰 수 있게 된다.

fetch('https://api.example.com/large-data')
    .then(response => {
        // 응답 본문을 스트림으로 가져온다.
        const reader = response.body.getReader();

        // 읽기 과정을 처리하는 함수
        function read() {
            return reader.read().then(({ done, value }) => {
                if (done) {
                    console.log('스트림 처리 완료');
                    return;
                }

                // 'value'는 Uint8Array입니다. 이를 사용해 필요한 처리를 한다.
                // 예: 데이터 조각 처리

                // 다음 데이터 조각을 읽는다.
                return read();
            });
        }

        // 스트림 읽기 시작
        return read();
    })
    .catch(error => console.error('Error:', error));

이 예제에서 fetch()는 네트워크 요청을 보내고, 응답을 스트림으로 받아온다. 'response.body.getReader()'를 호출하여 응답 본문을 조각별로 읽을 수 있는 'ReadableStream'의 'reader'를 생성한다. 'reader.read()' 코드는 Promise를 반환하며, 이는 스트림에서 다음 데이터 조각을 읽는다.

 

'done' 값이 'true'가 되면 스트림의 끝에 도달한 것이고, 'false'라면 'value'에 데이터 조각이 포함된다. 이 데이터를 사용하여 필요한 처리를 할 수 있으며, 스트림의 끝에 도달할 때까지 반복하는 코드이다.

 

이 방식은 특히 대용량 데이터를 처리할 때 유용하다. 예를 들어서, 큰 이미지나 비디오 파일, 큰 JSON 데이터 등을 서버로부터 받아올 때 메모리 사용량을 최적화하며 데이터를 처리할 수 있게된다. 스트림을 사용하면 전체 데이터를 한 번에 메모리에 로드하는 대신 필요한 만큼 조금씩 읽어 처리할 수 있게 되어 효율성이 향상된다.

 

Fetch API는 현대 웹 개발에서 HTTP 요청을 처리하는 표준 방식으로 자리 잡았으며, 대부분의 웹 어플리케이션에서 필수적으로 사용된다.

 

 

Fetch API와 fetch() 메서드

Fetch API랑 fetch() 함수는 밀접한 연관이 있지만, 약간의 차이가 있다.

 

Fetch API는 웹 브라우저에서 서버와의 HTTP 통신을 수행하기 위한 인터페이스의 집합을 의미한다. 이 API는 네트워크 통신을 위한 여러 기능과 개념을 포함하고 있다. Fetch API는 HTTP 요청을 보내고 받는 방법, HTTP 요청과 응답을 처리하는 방법, 헤더, 본문, CORS 설정 등을 다루는 데 필요한 모든 기능을 제공한다. 즉 FetchAPI는 fetch() 함수를 포함해 HTTP 통신을 수행하는데 필요한 전체 인터페이스와 메커니즘을 의미한다.

 

fetch()함수는 Fetch API의 일부로, HTTP 요청을 간편하게 보낼 수 있는 메소드이다. 주어진 URL로 네트워크 요청을 보내고, Promise 객체를 반환한다. 이 Promise는 요청의 응답을 처리하는 데 사용된다. fetch()함수는 Fetch API가 제공하는 많은 기능 중 가장 핵심적인 기능을 담당한다.

 

결론적으로 fetch() 함수는 Fetch API의 구성 요소 중 하나로, 이들은 서로 밀접하게 연결되어 있다.

댓글