JS 비동기(promise) (2024-11-21 수업)

짝은별·2024년 11월 21일

JS

목록 보기
16/23

비동기란?

우선 비동기반대말동기정상적으로 위에서 아래로 흐르는 순서로 작동하는 것을 의미한다
하지만 비동기란 그 흐름에서 약간 벗어나 따로 시간을 가지며 실행되는 것을 의미한다

그렇다면 비동기는 왜 필요한가?
우선 웹 브라우저의 통신 방식은 기본적으로 동기적인 통신을 한다
따라서 주어진 순서대로 일을 처리한다
하지만 이 경우 내부의 일부 영역변할 경우 서버전체를 전달하고 전체를 전달받는 경우가 생긴다
따라서 비효율적인 과정을 거치는 경우가 생긴다
하지만 비동기 통신의 경우 특정 부분이 변하면 그 부분만 전달하고 전달받아 효율성을 높일 수 있다

HTTP 상태

그렇다면 이때 서버와의 교류원활했는지 어떻게 확인할까?
바로 HTTP의 상태를 알 수 있는 상태코드로 확인할 수 있다
흔히 아는 404 NOT FOUND404의 상태코드를 가진 서버 교류 실패인 것이다
그렇다면 어떤 종류가 있는지 살펴보자

물론 이 외에도 다양한 상태코드들이 있지만
우리가 흔히 접할 수 있는 상태코드들만 모아보았다

통상적으로 200~300번대는 성공, 400~부터는 실패로 간주하는 느낌이다

CRUD

그렇다면 이러한 교류는 어떠한 명령어에 의해 혹은 어떤 행동을 하도록 조작되는 것일까?
바로 REST API Architecture에 의거하여 일어난다
CRUD는 다음과 같다

  • Create
  • Read
  • Update
  • Delete

이때 REST모든 기능을 사용할 수 있을때 RESTful이라고 부른다

그렇다면 서버에 전달할때는 어떤 행동지시해야하는지는 알지만 어떤 명령어지시하는지는 알아보지 않았다
명령어는 다음과 같다

  • POST : Create
  • READ : Get
  • PUT : Update
  • DELETE : Delete
  • PATCH : Update - 이는 위에 언급한 4개의 기본 CRUD와는 달리 특정 부분수정할 수 있게 해준다

이때 명령어변경이 가능하냐? 라고 물으면 서버와 소통을 할 때 명령어에 대한 variation이 넓어지면 그 만큼 혼동을 줄 수 있고, 응답 시간에 대한 속도를 저하시킬 수 있어 하지 않는다 라고 대답할 수 있다

참고로 RESTRepresentation State Transfer의 약자이다

ajax

ajax란? Asynchronous Javascript XML의 약자로 쉽게 말해 비동기 통신을 도와주는 역할을 한다

아까도 언급했듯 비동기 통신페이지의 변경서버와 교류 시 필요하다
따라서 필요한 상황은 다음과 같다

  • ajax web api 요청 : 서버에 데이터를 받아올 때
  • 파일 읽기 : 서버에 파일을 읽어와야 할 때
  • 파일 쓰기 : 클라이언트 단에서 데이터를 생성할 때
  • 파일 수정 : 클라이언트 단에서 데이터를 수정할 때
  • 파일 제거 : 클라이언트 단에서 데이터를 제거할 때
  • 암호화 / 복호화
  • 예약 작업 : 특정 날짜와 시간에 작업을 수행할 때

XHR

우선 xhr비동기 통신을 도와주는 Ajax api의 종류이다
따라서 이를 통해 CRUD를 수행할 수 있다

사용법

const xhr = new XMLHttpRequest(); // 서버와 통신하기 위한 객체 생성
xhr.open('CRUD' , 'URL'); // open을 통해 초기화 및 가져올 데이터의 URL지정
xhr.send('optionalBody'); // 서버에 어떤 행동을 할 것인지 보내기

우선 생성자 함수를 사용해 서버와 통신하기 위한 객체를 생성한다
그 후 open으로 원하는 위치에 해당하는 URL을 연결한다
참고로 CRUD 자리엔 아까 언급했던 POST,GET,PUT,DELETE,PATCH와 같은 명령어를 입력할 수 있다
그리고 URL에는 API 주소가 들어가면 된다
send는 이러한 행동서버에 전달역할을 수행하도록 한다
이때 optionalBody선택 사항이다(body가 필요한 명령과 아닌 명령어가 존재)

CRUD 명령어는 대소문자를 구분하지 않더라고 인식한다

body가 필요한 통신은 하나가 있는데 그건 바로 POST이다
이는 문서의 생성을 담당하기에 그렇다
이때 databody에 담아서, 통신에 대한 정보header에 담아서 전송한다

더미 API로 실험한 것은 다음과 같다

const END_POINT = 'https://jsonplaceholder.typicode.com/users';

function xhr() {
  const xhr = new XMLHttpRequest();
  
  const obj = { name: 'james', age: 35 };

  xhr.open('POST', END_POINT);

  xhr.send(obj);
}

와 같이 통신할 수 있다
하지만 결과는 반환되지 않는다
이유는 여러가지가 있지만 하나하나 살펴보자

readystate

readystate는 통신의 상태를 나타내준다
종류는 다음과 같다

  • 0 : uninitialized
  • 1 : loading
  • 2 : loaded
  • 3 : interactive
  • 4 : complete
    이때 통신이 완료4번이라면 행동을 실행하는 것이 안전하다
    따라서 이러한 readystate가 변할때마다 값을 내뱉어주는 이벤트인 readystatechange가 존재한다

이러한 방식으로 조건을 더해보면 코드는 다음과 같아진다

const END_POINT = 'https://jsonplaceholder.typicode.com/users';

function xhr() {
  const xhr = new XMLHttpRequest();
  
  const obj = { name: 'james', age: 35 };

  xhr.open('POST', END_POINT);
  
  xhr.addEventListener('readystatechange' , () => {
    const { status , response , readystate } = xhr; // 객체 구조분해 할당
    
    if(readystate === 4) {
      if(status >= 200 && status < 400) {
        const data = response;
        console.log(data);
      }
    }
  });

  xhr.send(obj);
}

만약 이렇게 작성하면 수정한 것이 제대로 출력되지 않을 수 있다
지금의 경우 id만 반영이 되어 출력된다

그렇다면 이번엔 어떤 것을 해결해야 할까?

우선 서버와 통신할때 파일의 형식을 맞춰서 주고 받아야 한다
따라서 JSON.parseJSON.stringify를 사용할 수 있다

JSON.parse / JSON.stringify

우선 이것을 사용한 코드를 살펴보자

const END_POINT = 'https://jsonplaceholder.typicode.com/users';

function xhr() {
  const xhr = new XMLHttpRequest();
  
  const obj = { name: 'james', age: 35 };

  xhr.open('POST', END_POINT);
  
  xhr.addEventListener('readystatechange' , () => {
    const { status , response , readystate } = xhr; // 객체 구조분해 할당
    
    if(readystate === 4) {
      if(status >= 200 && status < 400) {
        const data = JSON.parse(response);
        console.log(data);
      }
    }
  });

  xhr.send(JSON.stringify(obj));
}

이렇게 된다면 주고 받는 것을 string화 시켰다 받을 때는 다시 해석하여 전달받을 수 있다

setRequestHeader()

사실 이렇게 하면 제대로 된 통신이 일어나지 않는다
이유는 우리가 값을 전달할때 JSON.stringify()를 사용함으로써 이는 문자열을 전달받았다고 인식한다
따라서 header에 전달하는 값이 json형식이라는 것을 같이 알려줘야 한다

const END_POINT = 'https://jsonplaceholder.typicode.com/users';

function xhr() {
  const xhr = new XMLHttpRequest();
  
  const obj = { name: 'james', age: 35 };

  xhr.open('POST', END_POINT);
  
  xhr.setRequestHeader('Content-Type', 'application/json');
  
  xhr.addEventListener('readystatechange' , () => {
    const { status , response , readystate } = xhr; // 객체 구조분해 할당
    
    if(readystate === 4) {
      if(status >= 200 && status < 400) {
        const data = JSON.parse(response);
        console.log(data);
      }
    }
  });

  xhr.send(JSON.stringify(obj));
}

이렇게 작성하게 되면 정상적인 서버와의 통신이 이루어지는 모습을 확인할 수 있다
그렇다면 이 header에 작성한 setRequestHeader의 역할은 다음과 같다
첫 번째 인수로는 설정할 key를, 두 번째 인수로는 key의 value를 입력한다고 볼 수 있다
지금은 Content-Typeapplication/json으로 주어 전달하는 파일json형식임을 명시하는 것이다

이 외에 Access-Control-Allow-Origin이라는 key도 있는데 이를 *로 설정하게 되면 모든 정보에 대한 접근 권한을 신청하는 것이다
참고로 허락을 해주는 것은 신청을 받은 쪽에서 결정한다
또한 license를 설정하여 접근 권한을 넘겨 받는 경우도 있다

header 설정반드시 open 뒤에 해주어야 한다
이유는 너무 당연하게도 open으로 url 설정을 안했는데 그에 대한 정보 설정은 말이 안되기 때문이다

Promise

비동기 통신흐름을 담당하는 Promise이다
이는 전에 살펴봤던 setTimeout같은 흐름으로 작동하지만 매우 큰 차이가 있다
setTimeout은 우리가 설정한 값만큼만 타이머를 돌리지만 Promise비동기 동작이 끝남에 맞춰서 실행할 수 있다

사용법

let promise = new Promise(function(resolve,reject){}); // 생성자 함수로 선언

promise의 내부에는 stateresult라는 내부 property를 갖는다
이때 실행되기 전에는 state에는 pendingresult에는 undefined가 할당되어 있다

이후 promise실행하게 되면 결과가 성공실패로 나뉘는데
성공state에는 fulfilled가, result에는 value가 담기게 된다
실패 시에는 state에는 rejectedresult에는 error가 담긴다
(promise는 항상 성공과 실패의 경우만 갖는다)

참고로 이 내부 property에는 직접적으로 출력할 수단이 없다

.then / .catch / .finally

promise를 활용한 간단한 함수를 예시로 보자

const promise = new Promise((resolve, reject) => {
  resolve('hello');
});

이때 promise를 출력해보면 다음과 같은 결과가 나온다

이때 우리는 'hello'라는 값을 출력하고 싶다 그렇다면 .then을 이용하여 출력하면 되는 것이다

promise.then((res) => {
  console.log(res);
});

결과는 다음과 같다

깔끔하다

그렇다면 이 .then의 역할에 대해 더 자세히 알아보자
우선 .thenpromise객체value값을 전달받아 매개변수로 사용이 가능하다
따라서 res'hello'를 나타내게 되는 것이다
그렇다면 실패할 경우 .then은 어떻게 나타날까?

const promise = new Promise((resolve, reject) => {
  reject('hello');
});

promise.then((res) => {
  console.log(res);
});

에러를 반환하게 된다

.then('성공 함수' , '실패 함수')로 사용이 가능하다
이는 만약 성공했다면 성공함수를 실행하고 실패했다면 실패함수를 실행하는 것이다

또한 이는 다시 promise객체를 반환한다

const promise = new Promise((resolve, reject) => {
  resolve('hello');
});

const then = promise.then((res) => {
  console.log(res);
});

console.log(then);

따라서 똑같은 promise 함수로 다시 .then사용하는 것도 가능하다는 의미이다
이를 promise chaining이라고 부른다
사용법은 다음과 같다

const promise = new Promise((resolve, reject) => {
  resolve('hello');
});

promise
  .then((res) => {
    console.log(res);

    return promise;
  })
  .then((res) => {
    console.log(res);

    return promise;
  })
  .then((res) => {
    console.log(res);
  });

이때 .then은 반환되는 promise객체상태가 pending이므로 이미 실행된 혹은 실행시켜서 return을 해주어야 다음 .then정상적으로 작동한다

그렇다면 .catch와 .finally는 무슨 역할을 할까?

우리가 .then을 살펴봤을 때 첫 번째 인수로는 성공 함수를 두 번째 인수로는 실패함수를 전달받는다고 했었다
하지만 이때 실패함수를 전달받지 않는다면 error를 catch할 곳이 없어진다
그때 .then이 실행되고 에러가 있다면 .catch로 보내 에러를 핸들링할 수 있다

let promise = new Promise((resolve, reject) => {
  reject('hello');
});

promise
  .then((res) => {
    console.log(res);
  })
  .catch(console);

.catch.then(null , '실패')과 같은 역할을 한다

마지막으로 .finallypromise실행이 끝나고 성공과 실패의 여부에 상관 없이 실행됩니다

profile
FE(철 아님) 개발자 꿈꾸다

0개의 댓글