API란?
웹페이지의 통신
Promise 객체
구조 분해 할당
값의 복사
구조분해할당 적용
JSON 데이터 활용
렌더링 딜레이 개선
API란 어떠한 프로그램에서 제공하는 기능을 사용자가 활용할 수 있도록 만들어 둔 인터페이스입니다.
위 개념을 일상 생활과 연결지어 이해해 보도록 하자
우리가 접근하는 사이트 혹은 프로그램을 어떠한 식당이라고 가정해보자
그리고 그 식당에서 팔고 있는 메뉴들을 보여주는 메뉴판, 이것을 우리는 API 명세서라고 부릅니다.
우리가 사용할 수 있는 기능들이 무엇이 있는지 어떻게 사용하는지 적혀 있죠
그리고 그 메뉴를 주문하는 행위, 해당 사이트, 프로그램에게 어떠한 기능을 사용하겠다 라고 전달하는 행위를 요청(Request)이라고 합니다. 이때 요청을 전달하는 사람을 클라이언트(Client)라고 부르죠.
이때 그 주문, 요청을 받아서 실제로 음식을 조리해주는 요리사에게 주문을 전달해주는 직원과 같은 역할.
요청을 받아 데이터를 가공해주는 서버에게 어떤 요청이 들어왔는지를 전달해주는 것이 바로 API입니다.
그리고 우리가 돌려 받게 되는 음식, 데이터를 응답(Response)이라고 합니다.
API와 같이 다른 서버와 통신을 할 때는 특정한 규칙에 맞춰 그 통신이 이루어져야 합니다.
현재 웹에서 통신을 진행할 때 가장 흔하게 사용되는 HTTP에 대해서 알아보겠습니다.
Request message
이미지 가장 윗줄의 GET /data/2.5/weather? HTTP/1.1에 해당하는 부분은 start-line이라 부릅니다.
우리가 사용한 HTTP Method, 요청 URI, HTTP의 버전을 담고 있죠.
Request Header
그리고 그 아래의 Host부터가 요청 헤더에 속합니다.
Request header에는 다양한 정보가 담기게 되는데, 요청을 받는 서버의 이름, 서버의 버전, 전달하는 컨텐츠의 타입, 요청 날짜, 요청을 보낸 컴퓨터의 정보 등 수많은 내용이 담겨집니다.
그리고 Request header의 내용이 모두 종료되면 하나의 빈 줄로 Request body와 구분해 줍니다.
Request Body
Request body에는 우리가 서버로 혹은 다른 사용자가 우리의 서버로 전달하고자 하는 컨텐츠를 담게 됩니다.
이 때, 어떠한 메서드를 통한 요청인지에 따라 Request body를 담을 수 있는지, 없는지가 결정됩니다.
Response Message
HTTP/1.1 200 OK에 해당하는 부분은 status-line이라고 부릅니다. HTTP 버전과 상태 코드(Status code), 응답 메세지를 담고 있죠.
그리고 두번째 줄부터가 Response header에 속합니다.
Response Header
Response header도 Request header와 마찬가지로 응답 날짜, 응답을 전달한 서버의 이름, 서버의 버전, 컨텐츠의 타입 등을 담고 있습니다.
Response header의 내용이 모두 종료되면 하나의 빈 줄로 Response body와 구분해 줍니다.
Response Body
Response body에는 실제로 응답 리소스 데이터가 담겨져 있습니다.
HTTP Request Method와 HTTP Status Code는 이미지로 알아보자
각 대역 별로 여러가지 Status code 가 존재하며 대역마다 위 사진과 같은 특징이 존재한다 자세한 정보는 MDN을 찾아보자
자바스크립트는 한번에 하나의 기능만을 수행하는 싱글 스레드이기 때문에 동기적으로 동작한다!
그렇다면 아래 코드는 어떻게 동작하는지 알아보자
console.log("1")
setTimeout(() => {
console.log("2")
}, 2000)
console.log("3")
1과 3이 먼저 출력되고 2초 후에 2가 출력되는것을 볼 수 있다
분명 한번에 하나의 작업만 가능할텐데 어떻게 setTimeout함수의 실행을 건너뛰어 3이 먼저 출력 되었을까?
이에 대해 알기 위해서는 JavaScript의 실행 환경을 살펴봐야 합니다.
JavaScript의 실행 환경 내부에는 call stack과 callback queue라고 하는 영역이 존재합니다.
자바스크립트에서는
먼저 기본적인 함수들은 모두 call stack에 쌓이게 됩니다.
그리고 비동기 함수(Web APIs 포함)들은 모두 callback queue에 쌓이게 되죠.
call stack에 쌓인 기본 함수들은 먼저 담긴 함수들이 위에 쌓인 함수들의 종료를 기다리게 됩니다.
callback queue에 쌓인 비동기 함수들은 call stack이 비워져 있다면, 그 때 call stack으로 하나씩 옮겨집니다.
setTimeout(() => {
console.log("setTimeout call")
})
const func3 = function() {
console.log("func3 call")
}
const func2 = function() {
func3()
console.log("func2 call")
}
const func1 = function() {
func2()
console.log("func1 call")
}
func1()
위 코드를 실행하면 setTimeout 함수는 지연 시간을 지정해주지 않았어도 비동기 함수 이기때문에 callback queue 영역으로 옮겨집니다.
그래서 아래와 같이 함수가 담기게 됩니다.
call stack 영역에 쌓인 함수는 LIFO 방식으로 수행되기 때문에 func3, func2, func1 순서로 함수가 완료될 것입니다.
그리고 해당 함수들이 모두 완료되면 그 후에 callback queue에 담긴 setTimeout 함수가 call stack으로 옮겨져 실행되며 마지막으로 "setTimeout call" 메세지가 출력될겁니다.
한번에 하나의 동작만 수행하는 한계점을 해결하기 위해 callback 함수를 활용 하는 해결책이 있었지만 여러번 중첩 시 가독성이 떨어지는 콜백 지옥을 만날 수 있다
그래서 이러한 문제점을 해결하기 위해 Promise의 개념이 나타났습니다.
Promise는 현재는 얻을 수 없지만 추후 작업이 완료되면 받아올 수 있는 데이터에 대한 접근 수단의 역할을 해줍니다.
Promise 객체에 대해 알아보자
Promise 객체는 위 세가지 상태를 가지며
위 코드는 promiseTest라는 함수를 실행 했을 때, 그 반환 값으로 Promise 객체를 돌려받는 코드입니다.
Promise 객체를 생성할 때는 내부에 함수를 인자로 넣어줄 수 있는데, 이때 그 내부 함수는 resolver, reject를 매개변수로 받아올 수 있습니다.
비동기 처리가 완료되면(Fulfilled), resolver가 호출되고 비동기 처리에 실패하면(Rejected), reject가 호출됩니다.
그리고 위 함수를 보면 resolver의 실행을 2초간 지연시키고 있기 때문에
Promise 객체를 돌려 받은 뒤 2초가 지나기 이전에 참조하려 하면 Pending 상태임을 돌려받게 됩니다.
아직 resolver가 호출되지 않았기 때문입니다.
통신을 통해 응답을 받아오는 과정은 코드가 실행되는 과정보다 느린 경우가 대다수입니다.
그렇기 때문에 통신 또한 비동기 처리가 필요합니다.
const communicationResult = fetch(HTTP Request)
console.log(communicationResult)
위 코드 처럼 서버 통신을 요청한 경우 Promise 객체가 담겨져 있다
비동기 동작의 결과로 받아온 객체이다
아직은 동작의 결과를 받지 못했기 때문에 정상적인 응답인지, 에러일 지 알 수 없지만 언젠간 돌려줄 것이라는 약속이 담긴 객체죠.
Promise 객체가 반환되는 이유는 코드의 실행이 통신의 결과를 받는 것보다 느리기 때문입니다.
그렇기 때문에 우리는 통신의 결과를 특정 로직에 적용시키고 싶다면, 그 결과를 받아올 때까지 기다려줄 필요가 있습니다.
비동기 통신을 처리해주는 방법은 여러가지가 있는데, 우리는 then(), catch() 메서드를 사용해서 처리하는 방법을 알아보도록 하겠습니다.
fetch(HTTP Request)
// then() 메서드 내부에 익명 함수를 하나 만들고 그 안에서 매개변수로 데이터를 받을 수 있다
// then() 메소드를 사용 응답을 받아올 때까지 기다려서 정상적인 값 반환
.then((res) => {
console.log(res)
})
// 만약 통신에 문제가 생기거나 then() 수행 시 에러를 만난다면 catch() 메소드 실행
.catch((err) => {
console.error(err)
})
구조 분해 할당이란,
구조화 되어 있는 배열, 객체와 같은 데이터를 분해하여 각각의 변수에 다시 할당하는 것을 이야기합니다.
배열과 객체의 값을 각각의 변수에 담아 관리하고 싶다면, 즉, 값을 복사해주고 싶다면 어떻게 할 수 있을까요?
const arr = [ 1, 2, 3, 4, 5 ];
let one = arr[0];
let two = arr[1];
const obj = {
name: "otter",
gender: "male"
}
let userName = obj.name;
let userGender = obj.gender;
위 코드처럼 작성하면 각각의 값을 따로 담아 관리가 가능하지만
각각의 데이터를 모두 변수로 선언해 주어야 하기 때문에 비효율적이다
구조분해할당을 사용해보자
const arr = [ 1, 2, 3, 4, 5 ];
let [ one, two ] = arr; // 배열의 구조분해할당
const obj = {
name: "otter",
gender: "male"
}
let { name, gender } = obj // 객체 구조분해할당
구조분해할당을 사용하면 배열과 객체의 요소들을 하나하나 손쉽게 복사해줄 수 있었습니다.
그런데 만약 배열 자체를 복사하고 싶다면 어떻게 해야할까요?
const arr = [ 1, 2, 3, 4, 5 ];
const newArr = arr
이렇게 할 경우 arr과 newArr이 같은 주소값을 공유하기 때문에 복사라고 이야기하기엔 어려운 부분이 있었습니다.
그렇기 때문에 복사를 위해서 사용해줄 수 있는 연산자가 존재합니다.
const arr = [ 1, 2, 3, 4, 5 ];
console.log(...arr) // 1, 2, 3, 4, 5
스프레드 연산자라고 불리우며 하나로 뭉쳐있는 값들의 집합은 전개해주는 연산자이다
또한 스프레드 연산자를 통해 얕은 복사 (shallow copy)를 수행할 수 있다
const arr = [ 1, 2, 3, 4, 5 ];
const newArr = [ ...arr ];
이렇게 하면 기존에 arr 배열이 가지고 있던 주소값과 전혀 다른 별개의 새로운 배열이 newArr이라는 변수에 담기게 됩니다. (참조 타입 값의 복사 성공)
배열 뿐만 아니라 객체도 spread 연산자를 사용한다면 복사가 가능합니다.
const obj = {
name: "otter",
gender: "male"
}
const newObj = { ...obj }
위와 같이 얕은 복사시 주소값이 다르기 때문에 원본에 영향을 주지 않는다 하지만 중첩 객체는 얕은 복사가 적용되지 않는다 그렇다면 깊은 복사(deep copy)에 대해 알아보자
const obj = {
name: "otter",
gender: "male",
favoriteFood: {
first: "sushi",
second: "hamburger"
}
}
const copy = JSON.stringify(obj)
console.log(copy)
/* {"name":"otter","gender":"male"
,"favoriteFood{"first":"sushi","second":"hamburger"}} */
const deepCopy = JSON.parse(copy)
console.log(deepCopy)
/*
{
name: "otter",
gender: "male",
favoriteFood: {
first: "sushi",
second: "hamburger"
}
}
*/
위 코드 처럼 JSON.stringify()를 사용해 객체가 아닌 JSON 데이터 포맷으로 복사 후
JSON.parse()를 사용해서 다시 객체 형태로 바꿔 주어 주소값이 다른 객체가 생성되며 이러한 복사를 깊은 복사라 부른다
rest parmeter는 spread 연산자와 유사해 보이지만 차이가 있습니다.
함수의 매개변수(parameter)를 배열로 전달 받는 방식입니다.
매개변수를 정의하는 과정에서 rest parameter의 개념을 적용시키면(...rest) 함수를 호출하는 과정에서 다수의 전달인자(arguments)가 건네졌을 때 변화가 생깁니다.
const foo = function(one, two, ...rest) {
console.log(rest)
}
foo(1, 2, 3, 4, 5) // [ 3, 4, 5 ]
// 참고 사항
전달 인자 모두를 받아올수 있고 다른 이름으로 변경 가능하며 항상 마지막에 존재해야 한다!!
드디어 자바스크립트의 3주차가 모두 마치게 되었다 한 주동안 이론적인 부분을 배우며 새로운 개념들을 너무나 많이 알게 되었다 또한 예제 문제들을 통해 배웠던 부분을 응용해서 풀어보고 기능을 만들어 보았는데 이 부분은 따로 다뤄 보겠다
오늘은 여기까지 😄