🙄REST가 뭐에요
REST(REpresentational State Transfer)는 HTTP의 장점을 최대한 활용할 수 있는 아키텍처라고 합니다.
REST의 기본 원칙을 성실히 지킨 서비스 디자인을 RESTful하다라고 표현한다고 하네요
REST는 HTTP를 기반으로 클라이언트가 서버의 리소스에 접근하는 방식을 규정한 아키텍처이며
REST API는 REST를 기반으로 서비스 API를 구현한 것을 의미합니다.
REST의 3요소
이렇듯 REST는 자체 표현 구조로 구성되어 있어서 REST API만으로도 HTTP 요청 내용을 이해할 수 있습니다.
구성요소 내용 표현방법 자원 자원 URI(엔드포인트) 행위 자원에 대한 행위 HTTP 요청 메서드 표현 자원에 대한 행위의 구체적 내용 페이로드
REST API에는 중요한 설계 원칙이 두가지 존재합니다.
1. URI는 리소스를 표현하는 데 집중해야한다.
2. 행위에 대한 정의는 HTTP 요청 메서드를 통해 해야한다
따라서 URI에 행위에 대한 표현 (get)과 같은 단어가 들어가서는 안됩니다.
URI는 특정 리소스를 식별하는 통합 자원 식별자(Uniform Resource Identifier)를 뜻하며
하위 개념으로 URL이 있습니다.
HTTP 메서드의 종류는 다음과 같습니다.
HTTP 요청 메서드 종류 목적 페이로드 GET index/retrieve 모든/특정리소스취득 X POST create 리소스 생성 O PUT replace 리소스의 전체 교체 O PATCH modify 리소스의 일부 수정 O DELETE delete 모든/특정 리소스 삭제 X
🙄프로미스??
자바스크립트는 비동기 처리를 위해 콜백패턴을 사용하지만 콜백패턴은
가독성이 나쁘고 에러 처리가 곤란하며 여러개의 비동기 처리를 한번에 처리하는 것에 한계가 있습니다.
따라서 이를 해결하기 위해 프로미스라는 비동기처리 패턴을 도입했습니다.
예전에 열심히 공부했는데도 다시 보니 새롭네요
🙄
// get 요청을 위한 비동기 함수
const get = (url) => {
const xhr = new XMLHttpRequest()
xhr.open('GET' , url)
xhr.send()
xhr.onload = () => {
// onload 이벤트핸들러는 비동기함수임 따라서 get 함수는 get 요청을 전달하고 onload 이벤트 핸들러를 등록한뒤
// undefined를 반환하고 종료함 get 함수에는 return 문이 없기 때문
// onload 이벤트 핸들러는 따라서 get 함수가 종료된 후 실행을함
// 따라서 get 함수의 onload 이벤트 핸들러에서 서버의 응답결과를 반환하거나 상위 스코프 변수에 할당하면 동작이 기대대로 안됨
if (xhr.status == 200) {
console.log(JSON.parse(xhr.response))
}
else {
console.error( ` ${xhr.status} ${xhr.statusText}`)
}
}
}
// id가 1인 post를 취득
get('https://jsonplaceholder.typicode.com/posts/1')
/*
{
userid : 1
id : 1
title : ~~
body : dfs~~
}
이 예제에서 get 함수는 서버의 응답 결과를 콘솔에 출력합니다"
get 함수는 비동기 함수이며 비동기 함수를 호출하면
함수 내부의 비동기 처리가 완료되지 않아도 즉시 종료합니다
따라서 비동기 함수 내부의 비동기로 동작하는 코드에서 처리 결과를 외부로 반환하거나
상위 스코프의 변수에 할당하게되면 기대대로 동작하지 않습니다
*/
비동기 함수는 비동기 처리 결과를 외부에 반환할 수도 없고
상위 스코프의 변수에 할당할 수도 없습니다.
따라서 비동기 함수의 처리결과에 대한 후속 처리는 비동기 함수 내부에서 수행해야만합니다.
이때 비동기 함수를 범용적으로 사용하기 위해 비동기 함수에
비동기 처리 결과에 대한 후속 처리를 수행하는 콜백 함수를 전달하는게 일반적입니다.
위 예제를 동작하게 만들려면 다음과 같이 콜백패턴을 사용합니다.
// get 요청을 위한 비동기 함수
const get = (url,sucesscallback, failcallback) => {
const xhr = new XMLHttpRequest()
xhr.open('GET' , url)
xhr.send()
xhr.onload = () => {
if (xhr.status == 200) {
sucesscallback(JSON.parse(xhr.response))
// 서버의 응답을 콜백 함수에 인수로 전달하면서 호출하여 응답에 대한 후속처리를 함
}
else {
failcallback(xhr.status)
// 에러 정보를 콜백 함수에 인수로 전달하면서 호출하여 에러 처리를 함
}
}
}
// id가 1인 post를 취득
// 서버의 응답에 대한 후속처리를 위한 콜백함수로 console.log() , console.error()를 전달함
get('https://jsonplaceholder.typicode.com/posts/1', console.log, console.error)
콜백함수를 받을 인자를 만들어주고
인자로 함수를 전달해줍니다.
한두번 사용할때는 괜찮지만 만약 비동기처리를 여러번 해야한다면
콜백안에 콜백안에 콜백을 집어넣게되고 아주 가독성이 망가지게 될것입니다.
get('/step1' , a => {
get(`/step2${a}` , b => {
get(`/step3${b}` , c => {
get(`/step4${c}` , d => {
console.log(d);
})
})
})
})
//콜백헬의 전형적인 예시
이렇게요!
🙄콜백 패턴은 에러처리가 힘들다.
콜백 패턴은 에러처리가 곤란합니다.
try {
setTimeout(() => {
throw new Error('Error')
},1000)
}
//에러를 캐치하지 못함
catch (e){
console.error('캐치한 에러', e)
}
위 코드는 에러를 발생시킵니다. 왜냐하면
1. setTimeout이 호출되면 setTimeout 함수 실행컨텍스트가 콜스택에 푸시됩니다.
2. setTimeout은 비동기함수이기 때문에 콜백 화살표함수가 호출되는걸 기다리지않고 바로 종료됩니다.
3. 타이머가 만료되면 화살표함수가 태스크 큐로 푸시되고 이벤트 루프에 의해 콜스택으로 푸시됩니다.
4. setTimeout 함수의 콜백함수가 실행될 때 이미 setTimeout함수는 종료된 상태입니다
setTimeout함수의 콜백함수를 호출한것은 setTimeout함수가 아닙니다.
5. 에러는 호출자 방향으로 전파됩니다. 즉 콜스택의 아래 방향으로 전파가 됩니다
하지만 콜백 화살표함수는 settimeout 함수가 호출한게 아닙니다.
따라서 setTimeout함수의 콜백함수가 일으킨 에러는 catch 블록이 캐치하지 않습니다.
🙄그럼 프로미스를 사용하면 어떨까요?
// Get 요청을 위한 비동기 함수
const promiseGet = url => {
return new Promise((resolve,reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET' , url)
xhr.send()
xhr.onload = () => {
if(xhr.status === 200 ) {
resolve(JSON.parse(xhr.response))
//응답을 성공적으로 받으면 resolve 함수 호출
}
else{
reject(new Error(xhr.status))
//에러 발생시 reject 함수 호출
}
}
})
}
promiseGet('urlurlrurl')
//promiseGet 함수는 promise를 반환합니다.
위 함수는 함수 내부에서 프로미스를 생성하고 반환합니다.
비동기 처리는 promise 생성자 함수가 인수로 전달받은 콜백함수 내부에서 수행합니다.
비동기 처리가 성공하면 resolve 함수에 인수를 전달하면서 호출하고
실패하면 reject 함수에 인수로 전달하면서 호출합니다.
생성 직후 프로미스는 pending 상태이고 비동기 처리가 수행되면
프로미스의 상태정보 의미 상태 변경 조건 pending 비동기 처리가 아직 수행되지 않은 상태 프로미스가 생성된 직후 기본 상태 fulfilled 비동기 처리가 성공한 상태 resolve 함수 호출 rejected 비동기 처리 실패 reject 함수 호출
비동기 처리 결과에 따라 상태가 변경됩니다.
그리고 프로미스의 상태는 resolve or reject 함수를 호출하는 것으로 결정됩니다.
이중 fulfilled / rejected 상태를 settled 상태라고 하며
settled 상태는 비동기 처리가 수행된 상태를 의미합니다.
비동기 처리가 성공하면 비동기 처리 결과를 값으로 갖고
비동기 처리가 실패하면 프로미스는 rejected 상태로 변화하며 에러 객체를 값으로 갖습니다.
따라서 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체라고 할 수 있습니다.
🙄프로미스의 후속처리 메서드
프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속 처리가 필요합니다.
이러한 후속처리를 위해 프로미스는 then , catch, finally 메서드를 제공합니다.
이 메서드들은 프로미스의 비동기처리상태가 변화하면 후속 처리 메서드에 인수로 전달한
콜백 함수가 선택적으로 호출됩니다.
또한 모든 후속 처리 메서드는 프로미스를 반환하며 비동기로 동작합니다.
then then 메서드는 두개의 콜백함수를 인수로 전달받습니다.
첫번째 인수는 비동기처리가 성공했을때 호출되며 비동기처리결과를 인수로 받습니다.
두번째 인수는 비동기처리가 실패했을때 호출되며 에러객체를 인수로 받습니다.
then 메서드는 언제나 프로미스를 반환해요!catch 한개의 콜백함수를 인수로 전달받습니다.
catch 메서드는 프로미스가 rejected인 상태에서만 호출되거든요!
따라서 catch 메서드는 then(undefined, onRejected)와 동일하게 동작합니다.
그리고 then 메서드와 마찬가지로 언제나 프로미스를 반환해요!finally finally 메서드는 한개의 콜백함수를 인수로 전달받습니다.
finally 메서드는 프로미스의 성공 실패 여부와 상관없이 무조건 한번 호출됩니다.
그러니까 성공하든 실패하든 무조건 수행해야할 처리내용이 있을때 사용하면 유용합니다.
위 두 메서드와 마찬가지로 언제나 프로미스를 반환합니다.//fulfilled new Promise(resolve => resolve('fulfilled')) .then(v=> console.log(v) , e => console.error(e)) //fulfilled // rejected new Promise((_,reject) => {reject(new Error('rejected'))}) .then(v=> console.log(v) , e => console.error(e)) //error : rejected
이런식으로 사용할 수 있겠네요!
근데 then 메서드 두번째 인자를 주는거랑
catch 메서드 쓰는거랑 동작은 똑같은데 굳이 나눠질 필요가..?
가독성을 위해서 then에 두번째 콜백함수를 주는거보다
catch 메서드를 사용하는게 권장된다고 합니다.
🙄프로미스 체이닝
프로미스 체이닝은 프로미스의 후속처리 메서드를 여러개 체이닝하는걸 말합니다.
프로미스 후속처리 메서드들은 항상 프로미스를 반환하기 때문에
연속적으로 호출할 수 있는거죠!
const url = 'rurlrurlrujlr' // id가 1인 post의 userid 취득 promiseGet(`${url}/posts/1`) .then( ({ userId}) => promiseGet(`${url}/users/${userId}`)) .then(userInfo => console.log(userInfo)) .catch(err => console.error(err)) //위 예제는 첫번째 then -> 두번째 then -> catch 순서로 동작합니다. //두번째 then은 첫번째 then 메서드가 반환한 프로미스를 인수로 받습니다. //만약 후속처리 메서드의 콜백함수가 프로미스가아닌 값을 반환하더라도 암묵적으로 resolve, reject하여 //프로미스를 생성해 반환합니다.
프로미스는 위와 같은 프로미스 체이닝을 통해 후속처리를 하므로
콜백헬이 발생하지 않습니다만 프로미스도 콜백 패턴을 사용하기 때문에
콜백 함수를 사용하지 않는 것은 아닙니다.
이러한 문제는 async/await을 이용해 해결할 수 있습니다.
async/await을 이용하면 프로미스 후속처리 메서드 없이 마치 동기처리처럼
프로미스가 처리결과를 반환하도록 구현할 수 있어요!
하지만 async/await도 프로미스를 기반으로 동작합니다. 엥..
뭐 나중에 나오겠죠
🙄프로미스의 정적 메서드
프로미스는 주로 생성자 함수로 사용되지만 프로미스도 함수이고 객체이기 때문에
메서드를 가질 수 있습니다. Promise는 총 5가지의 정적 메서드를 제공합니다.
1.Promise.resolve / Promise.reject
위 메서드는 이미 존재하는 값을 래핑하여 프로미스를 생성하기 위해 사용합니다.
2.Promise.all
위 메서드는 여러개의 비동기처리를 병렬로 처리하기 위해 사용합니다.
따라서 promise.all 메서드는 프로미스를 요소로 갖는 배열과 같이 이터러블을 인수로 받습니다.
그리고 전달받은 모든 프로미스가 fulfilled 상태가 되면 모든 처리 결과를
배열에 저장해서 새로운 프로미스를 반환합니다.
3.Promise.race
위 메서드는 Promise.all 메서드처럼 프로미스를 요소로 갖는 배열과 같은 이터러블을 인수로 받습니다.
하지만 .race 메서드는 all메서드처럼 모든 요소가 fulfilled 되기를 기다리지않고
가장 먼저 fulfilled 상태가 된 프로미스의 처리 결과를 resolve하는 새로운 프로미스를 반환합니다.
4.Promise.allSettled
위 메서드는 프로미스를 요소로 갖는 배열과 같은 이터러블을 인수로 받습니다.
전달받은 프로미스가 모두 settled 상태가 되면 처리 결과를 배열로 반환합니다.
settled가 반환한 배열에는 모든 프로미스들의 처리결과가 담겨있습니다.
5,
🙄마이크로태스크큐?
프로미스의 후속 처리 메서드의 콜백함수는 태스크큐가 아닌
마이크로태스크 큐에 저장됩니다.
setTimeout(() => console.log(1),0) Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)) // 출력결과 2 3 1 // 프로미스의 후속처리 메서드의 콜백함수는 태스크 큐가 아닌 마이크로태스크큐에 저장되기 때문에 // 2->3->1 순으로 출력되게됨. // 근데 왜 위에 있는 setTimeout이 더 늦게 실행됨? -> 마이크로태스크큐의 우선순위가 태스크큐보다 높기 때문
콜백함수, 이벤트핸들러를 일시 저장한다는 점에서는 태스크 큐와 같지만
마이크로태스크 큐는 태스크큐보다 우선순위가 높기 때문에
이벤트 루프는 콜 스택이 비면 먼저 마이크로태스크 큐에 대기 중인 함수를 실행시킵니다.
그리고 마이크로태스크큐가 비워진 이후에 태스크 큐에 대기 중인 함수를 실행시켜요!
🙄fetch 메서드?
fetch 함수는 http 요청 전송 기능을 제공하는 클라이언트 사이드 Web API입니다.
XMLHttpRequest 객체보다 사용법도 간단하고 프로미스도 지원하기 때문에
비동기 처리를 위한 콜백 패턴의 단점에서 자유롭습니다.
문법은 다음과 같아요!
const promise = fetch(url [, options])
fetch 함수는 HTTP 응답을 나타내는 Response 객체를 래핑한 Promise 객체를 반환합니다.
fetch함수에 HTTP 요청을 전송할 URL만 전달하면 GET 요청을 전달해줍니다.
fetch 함수는 프로미스를 반환하니까 프로미스의 후속처리 메서드도 사용할 수 있습니다.
따라서 then 을 통해 resolve한 Response 객체를 받을 수 있고
Response 객체는 다양한 프로퍼티를 제공해줍니다.
fetch 함수는 에러처리에 주의해야합니다.
const wrongUrl = 'http://jsonplaceholder.typicode.com/XXX/1' fetch(wrongUrl) .then(() => console.log('ok')) .catch(() => console.log("err")) //ok가 출력됨 //fetch 함수가 반환하는 프로미스는 404 not found와 같은 HTTP 에러가 발생해도 // 에러를 reject 하지 않고 불리언 타입의 ok 상태를 false로 설정한 Response 객체를 resolve함 //즉 오로지 네트워크 장애, CORS 에러에 의해 요청이 완료되지 못한 경우에만 프로미스를 reject함
따라서 fetch함수를 이용할 때는 fetch 함수가 반환한 프로미스가 resolve한 불리언 타입의 ok 상태를
확인해서 명시적으로 에러를 처리해야합니다.
fetch(wrongUrl) .then((response) => { if (!response.ok) throw new Error(response.statusText) return response.json() }) .catch(() => console.log("err"))
axios는 이런 HTTP 에러도 reject하는 프로미스를 반환하기 때문에
모든 에러를 catch 에서 처리할 수 있어 편리합니다.
axios 쓰면 좋겠네요..
수고하셨습니다!
반응형
'javascript' 카테고리의 다른 글
요약 정리는 못 참지 않을까요? (19) async/await (1) | 2023.01.27 |
---|---|
요약 정리는 못 참지 않을까요? (18) 제너레이터와 async/await (0) | 2023.01.27 |
요약 정리는 못 참지 않을까요? (16) ajax와 json (0) | 2023.01.20 |
요약 정리는 못 참지 않을까요? (15) 비동기 프로그래밍 (0) | 2023.01.20 |
요약 정리는 못 참지 않을까요? (14) DOM (0) | 2023.01.17 |