네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'programming language/Javascript'에 해당되는 글 10건


반응형

fetch() 함수로 원격 API 호출하기

서버단에서 대신 API를 호출해주기 보다는 클라이언트 단에서 직접 API를 호출하는 경우가 많다. (소위 Ajax로 브라우저에서 직접 비동기로 HTTP 통신을 하기도 한다.)

 

원격 API를 간편하게 호출할 수 있도록 브라우저에서 제공하는 fetch() 함수에 대해 알아보자.

 

XML Http Request 방식

xmlhttprequest 객체를 이용한 정통적인 초창기 비동기 서버 요청방식이다. 코드가 복잡하고 가독성이 좋지 않다는 단점이 있다.

var httpRequest = new XMLHttpRequest();

httpRequest.onreadystatechange = function() {
  if (httpRequest.readyState == XMLHttpRequest.DONE && httpRequest.status == 200) {
    document.getElementById("text").innerHtml = httpRequest.responseText;
  }
}

httpRequest.open("GET", "ajac_intro_data.txt", true);
httpRequest.sent();

라이브러리

원격 API를 호출하면 request, axios, jQuery같은 라이브러리이다. 브라우저에서 fetch() 함수를 지원하기 전에 클라이언트 단에서 직접 HTTP 요청하고 응답받는게 복잡해서 라이브러리를 사용했다. 요즘은 라이브러리 없이도 브라우저에 내장된 fetch() 함수를 이용하여 원격 API를 호출할 수 있다. 라이브러리를 사용하면 자바스크립트 번들(bundle) 파일의 크기만 늘어난다.

 

fetch API 방식

이벤트 기반인 XMLHttpRequest와 달리 fetch API는 Promise 기반으로 구성되어 있어 비동기 처리 프로그래밍 방식에 잘 맞다. 그래서 then이나 catch와 같은 체이닝으로 작성할 수 있다는 장점이 있다.

 

fetch 사용법

// fetch('서버 주소') 는 웹 브라우저에 '이 서버 주소로 요청해줘' 라는 의미고,
// 뒤에 .then은 '요청이 끝나고나서 이 일을 진행해줘' 라는 의미다.
fetch('ajax_intro_data.txt')
  .then( response => response.text() )
  .tehn( text => { document.getElementById("#t").innerHtml = text; } );

fetch() 함수는 첫번째 인자로 url, 두번째 인자로 options 객체를 받고, Promise 타입의 객체를 반환한다. 반환된 객체는 API 호출이 성공했을 경우엔 응답 객체를 resolve 하고, 실패했을 경우에 예외객체를 reject 한다.

fetch(url, options) 
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

1. 기본적으로 http 메서드 중 GET으로 동작한다.

2. 개발자 도구 > 네트워크 탭에 가면 fetch로 들어온 데이터를 볼 수 있다.

 

fetch의 response 속성

fetch를 통해 요청을 하고 서버로부터 값을 응답 받으면 .then을 통해 함수의 인자에 넘기게 되는데 이 값은 Response 객체이다.

- response.status : HTTP 상태 코드 

- response.ok : HTTP 상태코드가 200~299일 경우 true

- response.body : 내용

- response.text() : 응답을 읽고 텍스트를 반환한다.

- response.json() : 응답을 JSON 형태로 파싱한다.

- response.formData() : 응답을 FormData 객체 형태로 반환한다.

- response.blob() : 응답을 Blob(타입이 있는 바이너리 데이터) 형태로 반환한다.

- response.arrayBuffer() : 응답을 ArrayBuffer(바이너리 데이터를 로우 레벨 형식으로 표현한 것) 형태로 반환한다.

 

응답 자료 형태 반환 메서드는 한번만 사용 할 수 있다.
만일 response.text()를 사용해 응답을 얻었다면 본문의 콘텐츠는 모두 처리 된 상태이기 때문에 뒤에 또 response.json() 써줘도 동작하지 않게 된다.

 

Fetch - CRUD 요청하기

메서드 역할
GET GET을 통해 해당 리소스를 조회
POST POST를 통해 해당 URI를 요청하면 리소스를 생성
PUT PUT를 통해 해당 리소스를 수정
DELETE DELETE를 통해 리소스 삭제

GET 메서드

존재하는 자원을 요청

단순히 원격 API에 있는 데이터를 가지고 올 때 쓰임

fetch 함수는 디폴트로 GET방식으로 작동하고 옵션 인자가 필요가 없다.

응답(response) 객체는 json() 메서드를 제공하고, 이 메서드를 호출하면 응답객체로부터 JSON 형태의 데이터를 자바스크립트 객체로 변환하여 얻을 수 있다.

fetch("https://jsonplaceholder.typicode.com/posts/1") // posts의 id 1인 엘리먼트를 가져옴 
  .then((response) => response.json())
  .then((data) => console.log(data))
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit↵suscipit recusandae consequuntur …strum rerum est autem sunt rem eveniet architecto"
}

GET 메서드

새로운 자원 생성 요청

폼 등을 통해서 데이터를 만들어보낼 때, 보내는 데이터의 양이 많거나, 비밀번호 등 개인정보를 보낼 때 사용

method를 POST로 지정해주고, headers 옵션으로 JSON 포맷으로 적용해야 함. body 옵션은 요청 데이터를 JSON 포맷으로 넣어줌.

fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "POST", 
  headers: { 
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Test",
    body: "I am testing!",
    userId: 1,
  }),
})
  .then((response) => response.json())
  .then((data) => console.log(data))

PUT 메서드 (전체 수정)

존재하는 자원 변경 요청

API에서 관리하는 데이터의 수정을 위해 PUT 메서드를 사용함

method 옵션만 PUT으로 설정하는 점이외에는 POST와비슷

아예 전체를 body의 데이터로 교체해버림

fetch("https://jsonplaceholder.typicode.com/posts", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Test" // 아예 title 엘리먼트로 전체 데이터를 바꿈. 마치 innerHTML같이.
  }),
})
  .then((response) => response.json())
  .then((data) => console.log(data))

PATCH 메서드 (부분 수정)

존재하는 자원의 일부 변경 요청

fetch("https://jsonplaceholder.typicode.com/posts/1", { // posts의 id 1인 엘리먼트를 수정
  method: "PATCH",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    title: "Test" // title만 바꿈. 나머지 요소는 건들지 않음.
  }),
})
  .then((response) => response.json())
  .then((data) => console.log(data))

DELETE 메서드

존재하는 자원 삭제 요청

보낼 데이터가 없기 때문에 headers, body 옵션이 필요가 없다.

fetch("https://jsonplaceholder.typicode.com/posts/1", {
  method: "DELETE",
})
  .then((response) => response.json())
  .then((data) => console.log(data))

Fetch - async / await 문법

fetch의 리턴값 response 는 Promise 객체이다. 따라서 await / async 문법으로 가독성높게 코딩할 수 있다.

fetch("https://jsonplaceholder.typicode.com/posts", option)
.then(res => res.text())
.then(text => console.log(text));

//await은 async함수내에서만 쓸수 있으니, 익명 async 바로 실행함수를 통해 활용합니다.
(async () => {
  let res = await fetch("https://jsonplaceholder.typicode.com/posts", option);
  let text = res.text();
  console.log(text);
})()

fetch 모듈화 - 사용성 개선하기

fetch() 함수는 사용법이 아주 간단하지만, 계속 사용하다보면 똑같은 코드가 반복된다는 것을 느낄 것이다.

예를 들어, 응답 데이터을 얻기 위해서 response.json()을 매번 호출하거나, 데이터를 보낼 때, HTTP 요청 헤더에 "Content-Type": "application/json"로 일일히 설정해줘야 되거나 써야 될게 많다.

한두번 fetch() 를 사용하는 정도는 문제가 안되지만, 여러번 사용할 일이 있으면 따로 헬퍼 함수를 만들어 모듈화를 해놓는게 나중에 정말 편하게 사용할수 있다.

 

아래 코드와 같이 POST 요청을 보낼 필요가 있다면 함수로 따로 미리 구성을 짜놓고, 인자로 값들을 전달해 바로 응답값을 받는 식으로 구성을 짜놓자.

async function post(host, path, body, headers = {}) {
  const url = `https://${host}/${path}`;
  const options = {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...headers,
    },
    body: JSON.stringify(body),
  };
  const res = await fetch(url, options);
  const data = await res.json();
  if (res.ok) {
    return data;
  } else {
    throw Error(data);
  }
}
 
post("jsonplaceholder.typicode.com", "posts", {
  title: "Test",
  body: "I am testing!",
  userId: 1,
})
  .then((data) => console.log(data))
  .catch((error) => console.log(error));

 

 

 

 

 출처 

https://www.daleseo.com/js-window-fetch

 

[자바스크립트] fetch() 함수로 원격 API 호출하기

Engineering Blog by Dale Seo

www.daleseo.com

https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-AJAX-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD-%EB%B0%8F-%EC%9D%91%EB%8B%B5-fetch-api-%EB%B0%A9%EC%8B%9D

 

 

[JS] 📚 자바스크립트 Fetch API 으로 AJAX 요청하기

자바스크립트 AJAX 요청 방식 저번 시간에 Ajax 가 무엇인지 알아보았다. 정통적으로 XMLHttpRequest() 객체를 생성하여 요청하는 방법이 있지만 문법이 난해하고 가독성도 좋지 않다. 따라서 이번시간

inpa.tistory.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

비동기, Promise, async, await 

비동기 - 동시에 여러 작업을 해야할 때

자바스크립트에서는 아무 일도 안하고 단순히 기다리기만 하는 함수가 있다.

바로 setTimeout 함수이다. 첫번째 인자는 기다린 후에 실행시킬 함수, 그 다음은 기다릴 밀리초이다.

console.log("모두에게 일을 시켜보자!");

setTimeout(() => {
  console.log("A: 일을 마쳤습니다!");
}, 1000);

setTimeout(() => {
  console.log("B: 일을 마쳤습니다!");
}, 1000);

setTimeout(() => {
  console.log("C: 일을 마쳤습니다!");
}, 1000);

console.log("일은 전부 시켜놓았다!");
모두에게 일을 시켜보자!
일은 전부 시켜놓았다!
A: 일을 마쳤습니다!
B: 일을 마쳤습니다!
C: 일을 마쳤습니다!

1초만의 시간만 걸린다. 일을 시켜놓기만 하면 알아서 진행한다.

 

우선, setTimeout 함수는 자체의 실행은 즉시 실행되고 리턴된다. setTimeout 함수가 즉시 종료되는 이유는 작업을 예약하는 일이 전부이기 때문이다. 이것에 관한 설명은 이벤트 루프를 참고하기 바란다.

 

setTimeout 함수를 통해 시간이 조금 걸리지만 기다리기만 하면 되는 작업을 흉내내 보았다.

1. setTimeout는 인자로 들어온 콜백 함수를 예약하기만 하고 바로 끝난다.

2. setTimeout에 의해 기다리는 동작은 본래의 코드 흐름과는 상관없이 따로 따로 독립적으로 돌아간다.

3. 이렇게 따로따로 독립적으로 돌아가는 작업을 비동기 작업이라고 한다.

 

어떤 작업들이 비동기로 진행될까?

브라우저에서는 ajax라 불리는 XMLHttpRequest 객체를 활용하여 비동기적으로 요청을 보내고 받을 수 있었고,

최근에는 Fetch API를 사용하는 방법이 늘고있다.

 

비동기 작업의 문제점 (1) - 흐름을 예측하기 어렵다.

동기적으로 동작한다는 건, 시간이 오래 걸리긴 하지만 무엇이 어떻게 진행될 지는 명확하다.

반면, 비동기 코드에서는 비교적 효율적이긴 하지만, 무엇이 어떤 순서로 진행될지 예측이 어렵다.

 

비동기 작업의 문제점 (2) - 콜백 지옥

비동기 작업 특성상, 각각의 비동기 작업이 끝났을 때 이어질 작업을 미리 부여하는 식으로 흐름을 제어한다.

비동기 작업이 차례대로 주루룩 이어져 있다면, 이는 콜백 지옥에 해당한다.

종종 콜백 지옥에 의해 Promise가 등장했다는 설명도 있다. (반은 맞고 반은 틀린 설명이다.) 콜백을 통해 다음 할 일을 정하는 개념은 Promise에서도 동일하다. 콜백 지옥이라는 상황이 벌어지는 이유는 비동기 작업을 관리한다는 개념 자체가 없어서 코드가 지저분해질 수 밖에 없기 때문이다.

 

Promise

Promise는 비동기 작업의 단위이다. Promise를 통해 어떻게 비동기 작업들을 쉽게 관리할 수 있는지 알아보자.

기본 사용법

우선 Promise로 관리할 비동기 작업을 만들 때에는 Promise에서 요구하는 방법대로 만들어야 한다. new Promise(...)

const promise1 = new Promise((resolve, reject) => {
  // 비동기 작업
});

1. 변수의 이름은 promise1이며, const로 선언했기 때문에 재할당이 되지 않는다. 하나의 변수로 끝까지 해당 Promise를 관리하는 것이 가독성도 좋고 유지보수 하기도 좋다.

2. new Promise(...)로 Promise 객체를 새로 만들었다. 생성자는 함수와 동일하게 동작하므로 괄호를 사용하여 함수를 호출한다.

3. 생성자는 특별한 함수를 인자로 받는다. 

이 특별한 함수는 공식문서에서 executor라는 이름으로 부른다. 이 함수에 대해 자세히 설명하면 다음과 같다.

1. executor는 첫번째 인수로 resolve, 두번째 인수로 reject를 받는다.

2. resolve는 executor내에서 호출할 수 있는 또 다른 함수이다. resolve를 호출하게 된다면 비동기 작업이 성공했다는 뜻입니다.

3. reject 또한 executor 내에서 호출할 수 있는 또 다른 함수이다. reject를 호출하게 된다면 비동기 작업이 실패했다는 뜻입니다.

 

Promise의 특징으로 new Promise(...)하는 순간 여기에 할당된 비동기 작업은 바로 시작된다.

함수는 으레 정의하는 시점과 호출하는 시점이 다르다고 했지만 new Promise(...)는 바로 호출한다.

비동기 작업의 특징은 작업이 언제 끝날지 모른다. 이 작업이 성공하거나 실패하는 순간에 뒷처리를 해줘야 한다.

Promise 가 끝나고 다음의 동작을 설정해줄 수 있는데 바로 then 메서드와 catch 메서드다.

- then 메서드는 해당 Promise가 성공했을 때의 동작을 지정한다. 인자로 함수를 받는다.

- catch 메서드는 해당 Promise가 실패했을 때의 동작을 지정한다. 인자로 함수를 받는다.

- 위 함수는 체인 형태로 활용할 수 있다. 

 

const promise1 = new Promise((resolve, reject) => {
  resolve();
});

promise1
  .then(() => {
    console.log("then!");
  }).catch(() => {
    console.log("catch!");
  });

executor로 새로운 Promise를 만든 다음 then과 catch를 이용하여 후속 동작까지 지정해줘야 한다.

위 코드의 실행 결과는 다음과 같다.

then!

resolve 부분을 reject로 수정하였다. 

const promise1 = new Promise((resolve, reject) => {
  reject();
});

promise1
  .then(() => {
    console.log("then!");
  })
  .catch(() => {
    console.log("catch!");
  });

이것의 실행결과는 다음과 같다.

catch!

재사용하기

new Promise(...)를 하는 순간 비동기 작업이 시작되는데, 비슷한 비동기 작업을 수행할 때마다 매번 new Promise(...)를 해주어야 할까? new Promise(...) 한 것을 그대로 리턴하는 함수를 만들어 사용하면 된다.

function startAsync(age) {
  return new Promise((resolve, reject) => {
    if (age > 20) resolve();
    else reject();
  });
}

const promise1 = startAsync(25);
promise1
  .then(() => {
    console.log("1 then!");
  })
  .catch(() => {
    console.log("1 catch!");
  });

const promise2 = startAsync(15);
promise2
  .then(() => {
    console.log("2 then!");
  })
  .catch(() => {
    console.log("2 catch!");
  });
1 then!
2 catch!

작업 결과를 전달하기

resolve, reject 함수에 인자를 전달함으로써 then 및 catch 함수에서 비동기 작업으로부터 정보를 얻을 수 있다.

function startAsync(age) {
  return new Promise((resolve, reject) => {
    if (age > 20) resolve(`${age} success`);
    else reject(new Error(`${age} is not over 20`));
  });
}

const promise1 = startAsync(25);
promise1
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });

const promise2 = startAsync(15);
promise2
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });
25 success
Error: 15 is not over 20
  at file
  at new Promise(<anonymous>)
  ...

기타 고려사항

- executor 내부에서 에러가 throw 된다면 해당 에러로 reject 가 수행된다.

- executor의 리턴값은 무시된다.

- 첫번째 resolve, reject만 유효하다.

const throwError = new Promise((resolve, reject) => {
  throw Error("error");
});
throwError
  .then(() => console.log("throwError succeess"))
  .catch(() => console.log("throwError catched")); //
  
const ret = new Promise((resolve, reject) => {
  return "returned");
});
ret
  .then(() => console.log("ret success")) //
  .catch(() => console.log("ret catched"))

// resolve 만 됩니다.
const several1 = new Promise((resolve, reject) => {
  resolve();
  reject();
});
several1
  .then(() => console.log("several1 success"))
  .catch(() => console.log("several1 catched"));

// reject 만 됩니다.
const several2 = new Promise((resolve, reject) => {
  reject();
  resolve();
});
several2
  .then(() => console.log("several2 success"))
  .catch(() => console.log("several2 catched"));

// resolve 만 됩니다.
const several3 = new Promise((resolve, reject) => {
  resolve();
  throw new Error("error");
});
several3
  .then(() => console.log("several3 success"))
  .catch(() => console.log("several3 catched"));

Promise의 의의

Promise는 세 가지 상태를 가진다.

- 대기 (pending)

- 이행 (fulfilled)

- 거부 (rejected)

이행 상태 일때 then, 거부 상태일 때 catch로 등록한 동작들이 실행됩니다. 

Promise를 생성한 이후에 상태가 실제로 어떤지 확인은 할 수 업다. 자바스크립트를 실행하는 브라우저 혹은 node.js에서 알아서 관리하기 때문이다.

Promise는 비동기 작업을 생성/시작하는 부분 (new Promise(...))과 작업 이후의 동작 지정부분 (then, catch)을 분리함으로써 유연한 설계를 가능토록 한다.

 

async - 비동기 작업을 만드는 손쉬운 방법

async 키워드는 함수를 선언할 때 붙여줄 수 있다. async 키워드가 붙은 함수를 async 함수, 없는 함수를 일반 함수라고 부르도록 하자. 

async 함수는 Promise와 밀접한 연관을 가지고 있는데, 기존에 작성한 executor로부터 몇가지 규칙만 적용한다면 new Promise(...)를 리턴하는 함수를 async 함수로 손쉽게 변환할 수 있다.

- 함수에 async 키워드를 붙인다.

- new Promise (...) 부분을 없애고 executor 본문 내용만 남긴다.

- resolve(value); 부분을 return value; 로 변경한다.

- reject(new Error(...)); 부분을 throw new Error(...); 로 수정한다.

 

// 기존
// function startAsync(age) {
//   retrurn new Promise((resolve, reject) => {
//     if (age > 20) resolve(`${age} success`);
//     else reject(new Error(`${age} is not over 20`));//   
//   });
// }

async function startAsync(age) {
  if (age > 20) return `${age} success`;
  else throw new Error(`${age} is not over 20`);
}

const promise1 = startAsync(25);
promise1
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });

const promise2 = startAsync(15);
promise2
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  });

실행하면 다음과 같다.

25 success
Error: 15 is not over 20
    at startAsync
    ...

놀랍게도 똑같이 동작한다.

이런 결론을 얻을 수 있다.

async 함수의 리턴값은 무조건 Promise이다.

우리는 async 함수를 일반 함수처럼 사용할 수 없다는 걸 절실히 깨닫게 되었다. 우리는 무조건 async 함수를 실행시킨 뒤 then과 catch 를 활용하여 흐름을 제어해야 한다.

한가지 사소하게 다른 점은, 에러메시지가 두 줄 줄어들었다. new Promise(<anonymous>) 부분이 사라졌다.

 

모든 비동기 동작을 async 함수로 만들 수 있는 건 아니다.

 

await - Promise 가 끝날 때까지 기다리라.

awaited는 Promise 가 이행되든지 거절되든지 끝날 때까지 기다리는 함수이다. await는 async 함수 내부에서만 사용할 수 있다.

function setTimeoutPromise(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay));
}

async function startAsync(age) {
  if (age > 20) return `${age} success`;
  else throw new Error(`${age} is not over 20`);
}

async function startAsyncJobs() {
  await setTimeoutPromise(1000);
  const promise1 = startAsync(25);
  
  try {
    const value = await promise1;
    console.log(value);
  } catch (e) {
    console.error(e);
  }
  
  const promise2 = startAsync(15);
  
  try {
    const value = await promise2;
    console.log(value);
  } catch (e) {
    console.error(e);
  }
}

startAsyncJobs();
// 1초후
25 success
Error: 15 is not over 20
    at startAsync
    at startAsyncJobs

1. 문법적으로 await [[Promise 객체]] 사용한다.

2. await은 Promise가 완료될 때까지 기다린다. 그러므로 setTimeoutPromise의 executor에서 resolve 함수가 호출될 때까지 기다립니다. 그 동안 startAsyncJobs의 진행은 멈춰있다.

3. await은 Promise 가 resolve 한 값을 내놓는다. async 함수 내부에서는 리턴하는 값을 resolve한 값으로 간주하므로 value엔 ${age| success가 들어간다.

4. 해당 Promise에서 reject가 발생한다면 예외가 발생한다. 이 예외를 처리하기 위해선 try-catch 구문을 사용한다. reject로 넘긴 에러 (async 함수 내에서는 throw한 에러)는 catch 절로 넘어간다. 

 

await는 왜 async 함수에서만 사용할 수 있을까?

비동기 작업으로부터 파생된 모든 작업은 비동기 작업으로 간주할 수 있다. 동기적으로 실행되는 프로그램에서 비동기 작업이 시작되었을 때, 그것을 기다리는 행위는 무의미하다.

반면, 비동기 환경에서 비동기 작업의 결과를 기다리는 것은 의미가 있다. 비동기는 동작 특성상 실제 작업과 그 작업의 후속 조치를 분리할 수밖에 없는데, async와 await를 쓰면 하나의 흐름 속에서 코딩할 수 있다.

 

.then 혹은 await가 없다면 어떻게 될까?

function setTimeoutPromise(delay) {
  return new Promise((resolve) => setTimeout(resolve, delay));
}

async function startAsync() {
  setTimeoutPromise(1000);
  setTimeoutPromise(1500);
  setTimeoutPromise(2000);
}

console.log("시작");

const promise = startAsync();
promise
  .then(() => {
    console.log("끝");
    process.exit(0);
  });

위 코드는 실행되자마자 아래 출력을 남기고 즉시 종료된다.

시작
끝

startAsync 함수의 문제점은 명확하다. setTimeoutPromise에 await를 걸지 않았다는 것이다. 

await 을 적절히 걸어야 한다는 감각은 처음 비동기를 익힐 때 쉽사리 적응되지 않는 감각이므로, 자주 쓰면서 습관을 만들어낼 수 밖에 없다.

 

 참고하면 좋은 글 

https://frogand.tistory.com/105

 

[ES6] 프로미스 (Promise)

1. 프로미스란? 자바스크립트는 비동기 처리를 위한 하나의 패턴으로 콜백 함수를 사용한다. 하지만 전통적인 콜백 패턴은 콜백 헬로 인해 가독성이 나쁘고 비동기 처리 중 발생한 에러의 처리

frogand.tistory.com

- 비동기 처리를 위해 콜백 패턴을 사용하면 처리 순서를 보장하기 위해 여러 개의 콜백 함수가 네스팅(nesting, 중첩)되어 복잡도가 높아지는 콜백 헬(Callback Hell)이 발생하는 단점이 있다. 

- 이때 비동기 처리가 성공하면 콜백 함수의 인자로 전달받은 resolve 함수를 호출한다. 이때 프로미스는 'fulfilled' 상태가 된다. 비동기 처리가 실패하면 reject 함수를 호출한다. 이때 프로미스는 'rejected' 상태가 된다. 

 

 출처 

https://springfall.cc/post/7

 

[Javascript] 비동기, Promise, async, await 확실하게 이해하기

봄가을 개발 블로그

springfall.cc

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

JSON.parse()와 JSON.stringify()

- 자바스크립트에서 JSON 내장 객체를 이용하여 JSON 형식으로 표현된 데이터를 다루는 방법

JSON이란?

JSON은 Javascript Object Notation의 약자로, 데이터를 문자열의 형태로 나타내기 위해서 사용된다. 이름이 보여주듯 JSON은 본래 자바스크립트에서 파생되었지만 현재는 거의 표준으로 자리잡아 대부분의 언어에서 지원하는 데이터포맷이다. JSON은 특히 네트워크를 통해 서로 다른 시스템들이 데이터를 주고 받을 때 많이 사용되기 때문에 어렵지 않게 접할 수 있다.

JSON으로는 객체, 배열, 숫자, 문자열, 불린(boolean), 널(null)과 같은 다양한 데이터를 나타낼 수 있다.

JSON 내장 객체

자바스크립트에서는 JSON 포맷의 데이터를 간편하게 다룰 수 있도록 JSON 이라는 객체를 내장하고 있다. 이 객체는 자바스크립트 코드를 브라우저에서 실행하든 Node.js 런타임에서 실행하든 상관없이 전역(global)에서 접근이 가능합니다.

JSON 내장 객체는 Javascript 객체와 JSON 문자열 간의 상호 변환을 수행해주는 두 개의 메서드를 제공한다.

- JSON.parse()

- JSON.stringify()

JSON.parse(): JSON 문자열을 Javascript 객체로 변환

JSON 문자열을 Javascript 객체로 변환할 때는 JSON 객체의 parse() 메서드를 사용한다. parse() 메서드는 JSON 문자열을 인자로 받고 결과값으로 javascript 객체를 반환한다.

const str = `{
  "name": "홍길동",
  "age": 25,
  "married": false,
  "family": { "father": "홍판서", "mother": "춘섬" },
  "hobbies": ["독서", "도술"],
  "jobs": null
}`;

JSON.parse() 메서드에 str을 인자로 넘겨 호출해 결과값을 obj라는 변수에 저장한다.

const obj = JSON.parse(str);

obj 에 저장된 값을 콘솔에 출력해보면 JSON 문자열 형태의 데이터가 Javascript 객체의 형태로 변환되어 출력되는 것을 확인하자.

console.log(obj);

{
    name: "홍길동",
    age: 25,
    married: false,
    family: {
        father: "홍판서",
        mother: "춘섬"
    },
    hobbies: [
        "독서",
        "도술"
    ],
    jobs: null
}

JSON 문자열에서는 key 를 나타낼 때 반드시 쌍따옴표로 감싸주어야 하지만, Javascript 객체에서는 쌍따옴표를 꼭 사용할 필요가 없다. 

Javascript로 변환된 데이터는 .이나 [] 기호를 활용하여 각 속성에 접근할 수 있다.

> obj.name
< '홍길동'
> obj.age
< 25
> obj.married
< false
> obj.family
< {father: '홍판서', mother: '춘섬'}
> obj.family.mother
< '춘섬'
> obj.hobbies
< ['독서', '도술']
> obj.hobbies[1]
< '도술'
> obj.jobs
< null

외부에서 문자열의 형태로 주어진 데이터를 해당 언어에서 다루기 용이하도록 내장 데이터 타입으로 변환하는 과정을 역직렬화(deserialization)이라고 한다. 클라이언트에서 JSON 포맷으로 데이터를 보내면 서버에서 우선 Javascript 객체로 변환 후, 데이터를 처리한다.

JSON.stringify(): Javascript 객체를 JSON 문자열로 변환

역으로 Javascript 객체를 JSON 문자열로 변환할 때는 JSON 객체의 stringify() 메서드를 사용한다. stringify() 메서드는 Javascript 객체를 인자로 받고 JSON 문자열을 반환한다.

const obj = {
  name: "홍길동",
  age: 25,
  married: false,
  family: {
    father: "홍판서",
    mother: "춘섬",
  },
  hobbies: ["독서", "도술"],
  jobs: null,
}; // javascript 객체

JSON.stringify() 메서드에 obj 를 인자로 넘겨 호출해보자. 

const str = JSON.stringify(obj);

console.log(str);
'{"name":"홍길동","age":25,"married":false,"family":{"father":"홍판서","mother":"춘섬"},"hobbies":["독서","도술"],"jobs":null}'

str에 저장된 값을 콘솔로 출력해보면 Javascript 객체의 형태인 데이터가 JSON 형식의 문자열로 변환되어 출력되는 것을 확인할 수 있다.

 

stringify() 메서드의 3번째 인자로 들여쓰기 할 공백의 크기도 지정해줄 수 있다.

const str2 = JSON.stringify(obj, null, 2);
console.log(str2);

{
  "name": "홍길동",
  "age": 25,
  "married": false,
  "family": {
    "father": "홍판서",
    "mother": "춘섬"
  },
  "hobbies": [
    "독서",
    "도술"
  ],
  "jobs": null
}

당연하게도, JSON 형식의 문자열로 변환된 데이터는 더이상 . 이나 [] 기호를 사용하여 속성에 접근할 수 없다.

> str.name
< undefined

특정 언어의 내장 타입의 데이터를 외부에 전송하기 용이하도록 문자열로 변환하는 과정을 직렬화(serialization)이라고 한다. 

 

 출처 

https://www.daleseo.com/js-json/

 

JSON.parse()와 JSON.stringify()

Engineering Blog by Dale Seo

www.daleseo.com

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

https://www.amcharts.com

 

JavaScript charting library - amCharts 4

The most innovative charting library on the market. Easily add stunning data visualizations to JavaScript, TypeScript, Angular, React, and other apps.

www.amcharts.com

 

Demo에 가보면 예제를 볼 수 있다.

그 중에서 나는 column-with-rotated-series를 사용하였다.

 

 

ajax로 데이터를 가져와서 Chart를 뿌려주는 것을 만들려고 한다.

 

data 형식은 아래와 같다.

var data = [
  {category: "USA", value: 2025}, 
  {category: "China", value: 1882}, 
  {category: "Japan", value: 1809}, 
	... 생략 ... 
  {category: "Germany", value: 1322}];

 

column-chart.js

var ColumnChart = function () {
  this.initEvents();
}

ColumnChart.prototype.initEvents = function () {

}

ColumnChart.prototype.getData = function (url, id) {
  var _this = this;

  $.ajax({
    url: url,
    method: "GET",
    dataType: "json",
    async: false,
    success: function(data) {
      _this.renderChart(id, data)
    },
    error: function(err) {
      alert("적용 중 에러가 발생하였습니다.");
      console.log(err);
    }
  });
}

ColumnChart.prototype.renderChart = function (chartId, data) {
  var root = am5.Root.new(chartId);

  root.setThemes([
    am5themes_Animated.new(root)
  ]);

  var chart = root.container.children.push(
    am5xy.XYChart.new(root, {
      panX: false,
      panY: false,
      layout: root.verticalLayout
    })
  );

  // y좌표 설정
  var yRenderer = am5xy.AxisRendererY.new(root, {});
  yRenderer.labels.template.setAll({
    fontSize: 13 // y좌표 폰트 크기 조정
  });

  var yAxis = chart.yAxes.push(
    am5xy.ValueAxis.new(root, {
      renderer: yRenderer
    })
  );

  // x좌표 설정
  var xRenderer = am5xy.AxisRendererX.new(root, { minGridDistance: 30 });
  xRenderer.labels.template.setAll({
    centerY: am5.p10,
    centerX: am5.percent(50),
    paddingRight: 0,
    fontSize: 13 // x좌표 폰트 크기 조정
  });

  var xAxis = chart.xAxes.push(am5xy.CategoryAxis.new(root, {
    maxDeviation: 0.3,
    categoryField: "category", // 카테고리 명을 적어주면 된다.
    renderer: xRenderer,
  }));

  xAxis.data.setAll(data);

  var tooltip = am5.Tooltip.new(root, {
    labelText: "{valueY}",
    autoTextColor: false // 문자 색상을 설정할 수 있다. autoTextColor: false로 색상 설정을 없앰.
  });

  var series = chart.series.push(
    am5xy.ColumnSeries.new(root, {
      name: "Chart Name",
      xAxis: xAxis,
      yAxis: yAxis,
      valueYField: "value",
      categoryXField: "category", // x좌표 categoryField와 일치시킨다.
      tooltip: tooltip
    })
  );

  series.columns.template.setAll({
    cornerRadiusTL: 5,
    cornerRadiusTR: 5,
    width: 32 // 컬럼의 너비 설정
  });

  // 컬럼 색상 설정 -> 기본으로 설정
  series.columns.template.adapters.add("fill", function (fill, target) {
    return chart.get("colors").getIndex(series.columns.indexOf(target));
  });
  series.columns.template.adapters.add("stroke", function (stroke, target) {
    return chart.get("colors").getIndex(series.columns.indexOf(target));
  });

  series.data.setAll(data);
  chart.set("cursor", am5xy.XYCursor.new(root, {}));
}

 

index.html

<div class="category-chart">
 <div class="row">
  <div class="box">
   <div class="category-name">Category</div>
   <div id="category-chart" class="chart"></div>
  </div>
 </div>
</div>

<script src="https://cdn.amcharts.com/lib/5/index.js"></script>
<script src="https://cdn.amcharts.com/lib/5/xy.js"></script>
<script src="https://cdn.amcharts.com/lib/5/themes/Animated.js"></script>
<script>
    var ColumnChart = new ColumnChart();

    $(function () {
      ColumnChart.getData('/category', 'category-chart'); // ajax로 data를 가져와 chart를 그려준다.
    })
</script>

 

chart.css

.category-chart {
  width: 100%;
}

.box {
  /* width: 48%; */
  background-color: rgba(238, 238, 238, 0.4);
  padding-top: 20px;
  padding-bottom: 16px;
  margin-left: auto;
  margin-bottom: 20px;
  border-radius: 10px;
}

.category-name {
  font-weight: bold;
  text-align: center;
  margin-bottom: 6px;
}

.chart {
  height: 300px; /* height를 꼭 지정해주어야 한다. */
}

 

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

자바스크립트를 사용하여 페이지를 새로고침하는 방법이다. 화면 개발을 하다보면 페이지 전체를 불러오거나 특정 영역을 갱신해야 하는 경우에 일반적으로 location을 사용한다. 특정 부분을 갱신하는 경우에는 iframe을 사용하거나 jQuery의 load를 사용하는 것이 좋다.

 

location.reload()

// location을 사용하는 방법
location.reload();
location.replace(location.href);
location.href = location.href;

// history를 사용하는 방법
history.go(0);

location은 페이지의 위치를 나타내기 때문에 location 방식을 권장한다. history는 페이지가 이동한 이력을 정의하고 있다. 

 

// Post 데이터를 포함해 페이지를 새로고침한다.
location.reload();

// Post 데이터는 포함하지 않으며 페이지를 새로고침한다.
// 이 때 현재 이력을 수정하며 페이지를 불러오기 때문에 history에 새로운 이력은 추가되지 않는다.
location.replace(location.href);

// 페이지를 이동한다. 이동할 페이지를 현재 페이지로 지정한다.
location.href = location.href;

reload() 함수는 옵션을 주어 실행할 수 있으며, 1개의 boolean 인자를 옵션 값으로 받는다. default 옵션값은 false이다. 

true로 설정하는 경우에는 브라우저가 가지고 있는 캐시를 무시하고 새로운 리소스를 화면에 불러온다.

location.reload(); // default: false

location.reload(true); // 브라우저가 가지고 있는 기존의 리소스는 신경쓰지 않고 새로운 리소스를 받아 화면을 갱신합니다.

 

location

location은 현재 Document에 대한 위치를 가지고 있는 객체로 location이나 window.location 또는 document.location를 통해서 접근이 가능하다.

 

location, window.location, document.location를 일치 연산자를 이용하여 비교해보면 동일하다는 것을 알 수 있다.

location === window.location // true
window.location === document.location // true

location, window.location, document.location은 동일하기 때문에 브라우저간의 호환성 문제가 있어 location의 사용에 문제가 있는 경우 아래와 같은 방법으로 페이지를 새로고침할 수 있다.

function reload() {
    (location || window.location || document.location).reload();
}
728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !