혼복필 2024. 2. 19. 00:25
728x90
300x250
SMALL

벌써 JavaScript 이론 시간의 마지막 단계인 Ajax입니다.

제가 처음 Ajax를 경험했던 건 Django 수업 때 같아요.

그때는 이게 무슨 말인가 했었는데 오랜만에 들어도 이게 무슨 소리인가 싶긴 했었습니다.

그래도 탄탄하게 이론을 다잡고 실습을 통해 직접 코드를 만져보면 괜찮아지겠다란 자신감은 품고 있어요 :)

 

AJAX (Asynchronous Javascript And XML)

비동기식 JavaScript와 xml의 약자

JavaScript를 이용해 서버와 브라우저가 비동기 방식으로 데이터를 교환할 수 있는 통신 기능

브라우저가 가지고 있는 XMLHttpRequest 객체를 이용해 전체 페이지를 새로 고치지 않고 페이지의 일부만을 위한 데이터를 로드하는 기법

= JavaScript를 통해 서버에 데이터를 비동기 방식으로 요청하는 것

 

동기 (Synchronous)와 비동기 (Asynchronous)

동기 : 요청과 결과가 동시에 일어나는 방식으로 요청을 보낸 후 응답을 받아야 다음 동작이 진행됨

요청과 결과가 한 자리에 동시에 나타나는 것

사용자가 서버로 요청을 보냈을 경우 요청에 대한 응답을 리턴 받기 전까지는 다른 것을 하지 못하고 기다려야 함

+ 장 : 설계가 간단하며 직관적임

+ 단 : 요청에 대한 결과가 반환되기 전까지 대기해야 함

비동기 : 요청과 결과가 동시에 일어나지 않는 방식으로 요청과 결과가 동시에 일어나지 않음

요청한 곳에 결과가 나타나지 않음

사용자가 서버로 요청을 보냈을 경우 요청에 대한 응답을 기다리지 않고 다른 것을 수행할 수 있으며 서버로 다른 요청을 보낼 수도 있음

+ 장 : 요청에 대한 결과가 반환되기 전에 다른 작업을 수행할 수 있어서 자원을 효율적으로 사용할 수 있음

+ 단 : 동기 방식보다 설계가 복잡하고 논증적임

 

 

+ 동기 코드

// JavaScript 코드는 기본적으로 순서대로 (동기적으로) 실행
console.log(1);
console.log(2);
[3, 4, 5].forEach(i => console.log(i));
console.log(6);

+ 비동기 코드

console.log(1);
// setTimeout으로 콜백함수가 일정시간 뒤에 실행하도록 코드를 작성

// 순서대로 실행되지 않음 (비동기적으로 실행)

// 이러한 비동기 실행 코드는 setInterval, addEventListener와 같은 함수들이 있음
setTimeout(() => console.log(2), 100);
[3, 4, 5].forEach(i => console.log(i));
console.log(6);

 

XMLHttpRequest

서버와의 비동기 통신을 가능하게 하는 여러 기능들을 가진 JavaScript 객체

// XHR 객체를 생성
const requestObj = new XMLHttpRequest();
requestObj.open('GET', 'url'); // 요청을 초기화, 통신방법과 요청을 발신할 대상의 주소를 전달
requestObj.onreadystatechange = () => { // readystate가 변화하면 실행되는 이벤트리스너
// readystate : 요청을 보내는 클라이언트의 상태를 의미
    // readystate의 종류
    // 0 (UNSENT) - XHR 객체가 생성되었지만 아직 초기화되지 않음
    // 1 (OPENED) - open() 함수가 호출되어 요청이 초기화됨
    // 2 (HEADERS_RECEIVED) - send() 함수가 호출
    // 3 (LOADING) - 데이터를 다운로드하는 중
    // 4 (DONE) - 통신 완료
    if (requestObj.readyState == 4 && requestObj.status == "200") {
        const result = requestObj.responseText;
    }
};
requestObj.send(); // 서버로 요청을 보냄

// send 메서드가 실행되어야만 우리가 위에서 설정한 내용들이 의미를 가지게 됨

 

+ requestObj.status

readyState : 서버와의 통신상태

status : 서버의 응답상태

200은 요청한 내용이 성공적으로 완료되었음을 의미

https://developer.mozilla.org/ko/docs/Web/HTTP/Status

 

readyState와 status

readyState

0 : 객체만 생성 (open 메서드가 호출되지 않음) = Uninitilalized

1 : open 메서드 호출 = Loading

2 : send 메서드 호출, status에 헤더가 도착하지 않은 상태 = Loaded

3 : 데이터의 일부를 받은 상태 = Interactive

4 : 데이터 전부 받은 상태 = Completed

status.stateText - 서버로부터의 응답 상태

200 : OK = 요청 성공

403 : Forbidden = 접근 거부

404 : Not Found = 페이지 없음

500 : Internal Server Error = 서버 오류 발생

 

CallBack

Ajax는 기본적으로 비동기적으로 서버와의 통신을 처리함

Ajax와 기존의 동기식 코드를 함께 작성하면 코드의 실행순서에 문제가 발생

= JavaScript 엔진은 비동기 코드가 끝날 때까지 다른 코드의 실행을 멈추지 않는 문제 발생

// sudo코드
const result = 비동기통신함수();
const result2 = 비동기통신함수2();
const total = result + result2;
// 이런 방식은 불가능

// result와 result2에 무슨 값이 들어있을지 생각해 볼 것

 

+ XHR 코드를 수정

let result;
function xhrRequest() {
    const requestObj = new XMLHttpRequest();
    requestObj.open('GET', 'message.txt');
    requestObj.onreadystatechange = () => {
        if (requestObj.readyState === 4 && requestObj.status === 200) {
            result = requestObj.responseText;
            console.log(result); // 콘솔에 무엇이 찍히는지 확인
        }
    };
    requestObj.send();
}
xhrRequest();
console.log(result); // 콘솔에 무엇이 찍히는지 확인

= 통신이 끝난 다음에야 다음 함수가 실행되도록 콜백함수로 코드를 작성해야 함

일반적인 동기식 코드처럼 비동기 함수 실행 후 다음 라인에서 다른 함수를 실행하는 순차적으로 함수들을 나열하는 방식은 불가능

비동기 코드가 끝나고 콜백으로 함수를 부르고 다음 함수를 또 콜백으로 부르는 형태가 되어야 함

const total = 비동기통신함수 ( 
        input,
        통신함수의 결과를 가공하는 함수1 ( 
            result, 
            비동기통신함수2 (
                통신함수의 결과를 가공하는 함수2 (
                    result, 
                    result2
                )
            ) 
        ) 
    );
// 이렇게 비동기 함수가 끝나기 전에 중간중간에 필요한 콜백함수를 실행시키며 사용할 수밖에 없음

 

fetch API

XMLHttpRequest를 대체할 새로운 API

+ XHR과 fetch의 차이점

XMLHttpRequest : 생성하는 인스턴스는 통신의 기능을 수행하는 XMLHttpRequest 객체를 반환

fetch : 인스턴스를 만들지 않고 약속을 반환함

+ Promise? 약속? 약속이란?

= 무언가를 할 것이라 미리 정하는 행위

// 커피를 주문하는 프로미스 객체를 생성

// 생성자에는 약속을 지키기 위한 resolve와, 약속을 지키지 못했을 때를 대비한 reject 두 가지를 인자로 전달
// 프로미스 객체를 생성하는 순간 프로미스 생성자함수의 콜백 함수가 실행

// 이를 실행자(executor)라 부름
const orderCoffee = new Promise((resolve, reject) => {
    const requestObj = new XMLHttpRequest();
    requestObj.open('GET', 'orderCoffee.txt');
    requestObj.onreadystatechange = () => {
        if (requestObj.readyState === 4) {
            if (requestObj.status === 200) {
                const result = requestObj.responseText;
                // resolve 메서드가 실행되면 then 메서드가 자동으로 호출
                resolve(result);
            } else {
                // resolve 메서드 호출이 없는 상태에서 reject 메서드가 실행되면 catch 메서드가 자동으로 호출
                reject(new Error(`커피주문이 정상적으로 이뤄지지 않았습니다.: ${requestObj.status}`));
            }
        }
    };
    requestObj.send();
});
// 이 부분에 주목

// then 메서드를 사용하면 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있음

// 앞에서 작성한 XHR 코드와 비교해 보는 것도 좋음
// resolve 메서드가 실행될 때 전달된 인자는 then 메서드의 콜백함수의 인자로 전달
orderCoffee.then((asyncResult) => {
    console.log(asyncResult);
    console.log('약속이 이루어졌습니다.');
    return asyncResult; 
}).catch((error) => { // then 메서드는 프라미스 객체를 반환하기 때문에 catch 메서드를 이어서 쓰는 것이 가능
// resolve 메서드와 마찬가지로 reject 메서드가 실행될 때 전달된 인자는 catch 메서드의 콜백함수의 인자로 전달
    console.log(error);
})

프로미스는 비동기 코드를 마치 동기적인 코드처럼 작성할 수 있음

= 약속을 의미하는 Promise 객체를 만들었고 이 약속은 이행되거나(fulfilled) 거절되거나(reject) 둘 중에 한 가지 결과만을 가지게 됨

이행되거나 거절되거나 둘 중의 하나의 결과는 전달될 거라 약속하고 작업을 진행하자는 개념

then과 catch를 이용해 동기적으로 코드를 이어 쓸 수 있는 것

 

fetch API 사용해 보기

let result = fetch('https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json');
console.log(result);

fetch는 함수처럼 바로 실행할 수 있음

1. fetch 함수를 실행하면 바로 인자로 전달한 url에 접근해 데이터를 다운로드

2. 다운로드가 완료되면 바로 fulfilled 상태의 프로미스가 반환

3. resolve 함수를 자동으로 호출

+ fetch 함수는 네트워크 오류가 발생하면 reject 상태의 프로미스를 반환

reject 함수를 자동으로 호출

콘솔로 result를 찍어보면 promise 객체가 보임

= promise 객체는 크게 두 가지 상황을 가정한 메서드 (then, catch)

let result = fetch('https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json');
result.then((data) => {
    console.log(data);
});

fulfilled 상태의 프라미스를 반환하기 때문에 then을 통해 데이터를 잘 가져올 수 있음

콘솔을 보면 이번에는 Response라는 객체가 보임

우리가 필요한 것은 JSON 데이터이기 때문에 .json() 메서드를 통해 JavaScript 객체로 변환이 가능한 promise 객체를 반환받을 수 있음

여기서 한번 더 then 메서드를 이용해야 우리가 필요한 데이터를 뽑아낼 수 있음

fetch('https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json')
.then((response) => {
// response.ok 는 응답이 성공적(200-299) 일 경우 true, 아니면 false를 반환
    if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
})
.then((data) => {
    console.log(data);
    return data;
})
// fetch 함수는 네트워크 오류가 발생하면 reject 상태의 프로미스를 반환
.catch((error) => {
    console.error(error);
});

 

+ response.ok

response.ok 는 응답이 성공적(200-299) 일 경우 true, 아니면 false를 반환

fetch의 경우 네트워크 통신이 완료되면 자동으로 fulfilled 상태의 프로미스가 반환

= 왜 예외처리가 필요한가?

fetch는 HTTP 응답 코드가 200이 아닌 '실패' 상태여도 resolve 상태의 프로미스를 반환

개발자가 의도한 데이터를 받지 못했지만, 서버와의 통신 자체는 성공했기 때문에 약속이 이행됐다고 판단하기 때문

때문에 사용자가 의도한 데이터를 무사히 전달받기 위해서는 response.ok를 이용한 예외처리가 필수적

 

async, await

async : 어떤 함수든 프로미스 객체를 반환하게 만들 수 있음 (return 키워드가 없어도 가능)

function test() {
    return Promise.resolve();
}

// 아래의 코드도 똑같이 동작함

async function test(){}

await : async 함수 안에서 promise 객체의 상태가 결정될 때까지 다음 코드를 실행시키지 않고 기다리게 만들어줌

프라미스 객체의 fulfilled 값을 반환하고 반드시 async 함수 안에서만 사용할 수 있음

async function message() {
    const hello = await new Promise((resolve) => {
        setTimeout(() => {
            resolve('hello');
        }, 100)
    })
    const world = await new Promise((resolve) => {
        setTimeout(() => {
            resolve('world');
        }, 100)
    })
    console.log(`${hello} ${world}`);
}
message();

위 코드를 통해 알 수 있는 await의 중요 특징

= async 함수 안에서 코드의 실행 순서를 확정 지을 수 있다는 것

비동기적 코드를 동기적으로 전환함으로써 async를 이용하면 기존의 Promise를 이용하는 것보다 더 가독성 있는 코드를 만들 수 있음

Promise만 이용하도록 변경해 보면 더 명확해짐

 

이렇게 JavaScript 이론이 끝났습니다.

조만간 비동기 (콜백함수, 프로미스, await/async, fetch)에 대해 자세히 글을 올려보도록 하겠습니다.

JavaScript는 이번에 진행한 프로젝트를 통해 많이 배울 수 있었던 것 같습니다.

다음에 진행될 Django가 벌써부터 걱정이네요.. t_t

Ajax에 관한 코드는 GitHub 참고해 주세요.

https://github.com/soohyun020812/ormcamp

 

GitHub - soohyun020812/ormcamp: 오름캠프 교육에서 활용한 실습 내용들 정리

오름캠프 교육에서 활용한 실습 내용들 정리. Contribute to soohyun020812/ormcamp development by creating an account on GitHub.

github.com

728x90
300x250
LIST