자바스크립트 클로저(Closure)와 Fetch를 사용한 비동기 처리

안녕하세요. 이번 시간에는 자바스크립트에서 비동기 처리를 위해 자주 활용되는 클로저(closure), AJAX, Fetch에 대해 다뤄보려고 해요. 이 함수들은 모던 웹 개발에서 많이 사용되는 기능 중 하나이기 때문에 알아두면 유용하게 활용하실 수 있어요! 먼저 개념부터 하나하나 알아볼게요.

1. 클로저 - Closure

클로저란, 함수가 선언될 당시의 *환경을 기억하을 하는 기능을 말해요. 자바스크립트에서는 함수가 호출될 때마다 새로운 환경이 생성되는데, 이 환경은 함수 내부의 변수와 함수 매개변수, 그리고 외부에서 전달된 인자 등을 포함하죠. 하지만, 함수 내부에서 선언된 변수는 외부에서 접근할 수 없어요.

이때, 클로저를 사용하면 함수 내부에서 선언한 변수에 접근할 수 있답니다. 이렇게 외부에서 접근할 수 있게 되어, 함수의 재사용성을 높일 수 있고, 함수 내부에서 선언된 변수를 은폐함으로써, 데이터의 보안성을 높일 수도 있어요.

환경이란? 렉시컬(Lexical) 환경을 말해요. 자바스크립트 엔진이 변수나 함수를 해석하고 관리할 때 사용하는 데이터 구조인데요. 이 환경은 코드가 실행될 때 생성되고, 그때의 스코프(Scope)와 변수, 함수 등을 저장해요. Scope는 함수가 선언된 위치와 함수 내부에서 선언된 변수 등에 따라 결정됩니다.

이해하기 쉽게 예제를 만들어볼게요.

// 외부 함수인 outer를 선언
function outer() {
    // 외부 함수 내부에 변수 count를 선언하고 0으로 초기화. 
    let count = 0;
    // 외부 함수 내부에 inner 함수를 선언
    function inner() { 
        // inner 함수는 외부 함수의 변수 count를 1 증가
        count++;
        // inner 함수는 변수 count의 값을 반환
        return count; 
    }
    // 외부 함수인 outer는 내부 함수 inner를 반환
    return inner; 
}
// outer 함수를 호출하고 반환값인 inner 함수를 counter 변수에 할당
const counter = outer();

console.log(counter()); // 1 (count가 0에서 1로 증가) 
console.log(counter()); // 2 (count가 1에서 2로 증가)

위 예제에서 outer라는 외부 함수가 있고, 그 안에 inner라는 내부 함수가 있어요. outer 함수를 호출하면 inner 함수가 반환되어 counter 변수에 저장되죠. 이후 counter()를 호출하면, inner 함수가 실행되고 count 변수가 1씩 증가한 값을 반환합니다. 여기서 중요한 점은 outer 함수의 실행이 끝나고 inner 함수가 반환되어도 count 변수가 사라지지 않고 계속 유지된다는 거예요. 이렇게 외부 함수의 변수에 접근할 수 있는 내부 함수를 클로저라고 합니다.

또 다른 예제를 볼까요?

// x를 매개변수로 받는 add 함수 선언
function add(x) { 
   // y를 매개변수로 받는 익명 함수를 반환
    return function(y) { 
        return x + y;   // x와 y를 더한 값을 반환
    };
}
var addFive = add(5); // add 함수에 5를 전달하여 반환된 함수를 addFive 변수에 할당
console.log(addFive(3)); // addFive 함수를 호출하여, 내부에서는 x가 5인 상태에서 3을 더한 결과인 8을 출력

위의 코드에서 add 함수는 x 값을 인자로 받아서, y 값을 인자로 받는 내부 함수를 반환해요. 반환된 내부 함수는 x 값을 클로저를 통해 접근할 수 있으므로, addFive 변수에 할당된 add(5) 함수는 이전에 전달된 x 값인 5를 기억하고 있죠. 따라서 addFive(3)은 5 + 3으로 8을 반환하게 되는 거죠.

클로저는 자바스크립트에서 스코프와 변수의 관리를 다룰 때 자주 사용되는 개념이에요. 따라서, 웹 개발에서 자주 사용되는 자바스크립트 라이브러리나 프레임워크에서 클로저를 사용하는 경우가 많아요.

2. 프로미스 - Promise

Promise는 자바스크립트 비동기 처리를 위한 객체에요. Promise를 사용하면 콜백 함수를 사용하지 않고도 비동기 처리를 보다 간편하게 할 수 있어요. 이를 통해 코드가 더욱 깔끔하고 가독성이 높아지죠. Ajax를 사용하기 전에 먼저 배우고 넘어가는 게 좋아요. 이유는 AJAX 예제에서..

// 비동기 작업을 수행하는 함수 선언
function doAsyncWork() {
    // Promise 객체를 생성하여 반환
    return new Promise(function(resolve, reject) {
        // 50%의 확률로 작업이 실패
        if (Math.random() < 0.5) {
            reject(new Error("비동기 작업 실패!"));
        }
        // 2초 후에 작업이 성공적으로 완료
        setTimeout(function() {
            resolve("비동기 작업 완료!");
        }, 2000);
    });
}
// Promise 객체를 생성
var promise = doAsyncWork();

// Promise 객체의 then 메서드를 사용하여 성공 시 처리할 콜백 함수를 등록
promise.then(function(result) {
    console.log("Success: " + result);
}).catch(function(error) {
    console.error("Error: " + error);
});

resolve와 reject는 Promise 객체를 생성할 때 함수 인자로 전달되는 함수에요. resolve 함수는 비동기 작업이 성공적으로 완료되었을 때 호출되며, reject 함수는 비동기 작업이 실패했을 때 호출돼요.

또 다른 방식으로 then 메서드와 catch 메서드를 연속적으로 호출하여 처리할 수 있어요.

var promise = doAsyncWork();

// then 메서드를 연속적으로 호출
promise.then(function(result) {
    console.log("first then:", result);
    return result;
}).then(function(result) {
    console.log("second then:", result);
}).catch(function(error) {
    console.error("Error: " + error);
});

 

Promise에서 then 메서드를 호출하여 반환된 Promise 객체에 대해 다시 then 메서드를 호출하는 것을 Promise 체이닝(chain)이라고 해요. 이를 통해 비동기 작업의 처리 결과를 체인 형태로 연결하여 처리할 수 있어요.

//아래와 같이 사용할수도 있습니다.
var promise = doAsyncWork();
promise.then(function(result) { console.log(result); });
promise.catch(function(error) { console.error(error); });

 

3. 아작스, 에이잭스 - AJAX

AJAX란 Asynchronous JavaScript and XML의 약자로, XMLHttpRequest 객체를 사용해서 비동기적으로 서버와 데이터를 교환하는 기술이에요. 즉, 페이지를 새로고침하지 않고도 서버로부터 데이터를 받아와 화면을 갱신할 수 있게 해줍니다.

요청과 응답을 처리하기 위해 콜백 함수를 사용하지만 콜백 함수를 사용하면 코드의 가독성이 저하되고, 비동기적으로 처리되는 작업의 흐름을 이해하기 어려워질 수 있어요.

그래서 콜백 지옥(callback hell)이라 불리는 코드 구조를 개선하기 위해 Promise가 등장했는데요. 비동기 작업의 결과를 다루는 데 유용하며, 요청과 응답을 처리하는 데 콜백 함수를 대신하여 사용할 수 있어요.

아래는 Promise를 이용한 AJAX 예제입니다.

// XMLHttpRequest 객체를 생성합니다.
const xhr = new XMLHttpRequest();

// Promise 객체를 생성합니다.
const promise = new Promise((resolve, reject) => {
  // 요청이 완료되면 실행될 콜백 함수를 설정합니다.
  xhr.onreadystatechange = function() {
    // 요청이 완료되었고 응답코드가 200이면 resolve 함수를 호출합니다.
    if (this.readyState == 4 && this.status == 200) {
      resolve(this.responseText);
    } else if (this.readyState == 4 && this.status != 200) {
      // 응답코드가 200이 아니면 reject 함수를 호출합니다.
      reject(new Error(`Request failed with status ${this.status}`));
    }
  };
});

// 요청을 보냅니다.
xhr.open("GET", "https://example.com/api/data", true); //test url
xhr.send();

// Promise 객체를 사용하여 요청이 완료되면 처리합니다.
promise.then(data => {
  console.log(data);  // 응답 데이터를 출력합니다.
}).catch(error => {
  console.error(error);  // 예외가 발생하면 에러 처리합니다.
});

readyState와 status는 XMLHttpRequest 객체의 프로퍼티로, 요청 상태와 응답 코드를 반환합니다.

readyState - 요청 상태

  • UNSENT - 아직 open() 메서드가 호출되지 않은 상태입니다.
  •  OPENED - open() 메서드가 호출된 상태입니다. 요청 헤더를 설정하는 등의 작업이 가능합니다.
  • HEADERS_RECEIVED - 서버로부터 응답 헤더를 받은 상태입니다. 응답 헤더를 이용하여 응답 유형이나 인코딩 등을 확인할 수 있습니다.
  • LOADING - 서버로부터 응답 본문을 받는 중인 상태입니다. 응답이 큰 경우, 본문을 조금씩 받아서 처리할 수 있습니다.
  • DONE - 요청 처리가 완료된 상태입니다. 응답이 완전히 받아졌으며, 클라이언트 측에서 처리할 수 있는 상태입니다.

​status - HTTP 응답 코드

  • 200: OK - 요청이 성공했음을 나타냅니다.
  • 201: CREATED - 요청이 성공적으로 처리되어 새로운 리소스가 생성되었음을 나타냅니다.
  • 400: BAD REQUEST - 클라이언트의 요청이 잘못되었음을 나타냅니다.
  • 401: UNAUTHORIZED - 인증되지 않은 요청을 나타냅니다.
  • 404: NOT FOUND - 요청한 리소스를 찾을 수 없음을 나타냅니다.
  • 500: INTERNAL SERVER ERROR - 서버에서 오류가 발생했음을 나타냅니다.

4. 패치 - Fetch

Fetch는 웹 API 중 하나로, 서버로부터 데이터를 받아오기 위해 사용돼요. Promise 기반으로 구현되어 있어 비동기적으로 데이터를 처리할 때 간결하게 코드를 작성할 수 있어요. Ajax도 비슷한 기능을 제공하지만 Promise를 지원하지 않으므로 비동기 처리를 위해 콜백 함수를 사용해야 하죠.

// fetch 함수로 가져올 API의 URL 설정
const apiUrl = 'https://example.com/api/data'; //test url

// fetch 함수를 사용하여 API 요청을 보냅니다.
fetch(apiUrl ) 
    .then(response => { 
        if (!response.ok) { // 응답이 정상적이지 않으면 에러
            throw new Error("Network response was not ok");
        }
        return response.json(); // 응답 데이터를 JSON 형식으로 파싱한 결과 반환
    })
    .then(data => console.log(data)) // 두 번째 콜백 함수에서 파싱된 JSON 데이터를 받음 
    .catch(error => console.error("Error fetching data:", error)); // 예외가 발생하면 에러 처리

Fetch API는 XML 외에도 다양한 데이터 형식을 지원하고, 전통적인 AJAX 방식보다 더 간편하게 비동기 요청을 처리할 수 있어서 최근에는 Fetch가 더 많이 사용되고 있답니다.

5. 클로저를 사용한 비동기 Fetch 요청 처리

// 클로저를 이용한 데이터를 저장 함수
function fetchData() {
    // 클로저 변수로 데이터를 저장할 변수를 선언합니다.
    let data = null;
    // 클로저 함수를 반환합니다.
    return function(url) {
        // 데이터가 없을 때만 API에서 데이터를 가져옵니다.
        if (!data) {
            // fetch를 이용하여 API에서 데이터를 가져옵니다.
            data = fetch(url) // test url
                .then(response => response.json()) // JSON 형식으로 변환합니다.
                .then(json => {
                    data = json; // 데이터를 변수에 저장합니다.
                    return json; // 데이터를 반환합니다.
                })
                .catch(error => console.error(error)); // 에러 처리합니다.
        }
        // 데이터가 있을 때는 변수에서 데이터를 반환합니다.
        return data;
    };
}
// fetchData 함수를 호출하여 데이터를 가져오는 함수를 생성합니다.
const getData = fetchData();

// getData 함수를 호출하여 데이터를 가져옵니다.
getData('https://example.com/api/data').then(data => {
    console.log(data); // 데이터를 콘솔에 출력합니다.
}).catch(error => console.error(error));

위 예제는 클로저를 이용하여 데이터를 저장하고, 필요할 때마다 데이터를 가져오는 함수에요. 먼저 getData는 fetchData() 함수를 호출해서 생성을 해요. 그리고 이 함수를 호출하면, 클로저 함수가 반환되며 내부에서 변수 data를 참조하죠.

 

데이터가 없는 경우, API에서 데이터를 가져와 변수 data에 저장하고, 데이터가 있으면 변수에서 데이터를 반환해 줘요.

이렇게 클로저를 이용하면 API 호출 횟수를 줄일 수 있어요. 또한 불필요한 데이터 요청을 방지하고 데이터를 안전하게 보호할 수 있답니다.

 

 

이번 포스팅을 통해 클로저, AJAX, Fetch에 대해 간단하게 알아보았어요. 이 함수들은 웹 개발에서 매우 중요한 역할을 하기 때문에 알아두시면 도움이 될 거예요. 앞으로도 다양한 웹 개발에 활용할 수 있는 지식을 공유하도록 할게요.  - 끝 -