프로미스 도대체 왜쓸까요?
가장 큰 이유는, 콜백헬을 다루기 위해서..! 그리고 비동기코드를 순차적으로 실행하기 위해서!!
그..그것이.. 프로미스니까..!
● 비동기 처리를 위한 콜백 패턴의 단점
비동기 함수는 '언제 실행될지 모르는 것' 이라는 점을 가지고 있다. 언제 실행될지 모르니, 비동기 함수 바깥에 있는 변수를 수정할 수도 없고, 비동기함수가 내뱉는 반환값도 가져올 수가 없다.
let a = 1;
// 0초뒤 a = 2로 선언한다.
setTimeout(()=> {a = 2;}, 0)
console.log(a) // 1
원래 예상하는 대로라면, 2가 출력되야하지만, 1이 출력된다. 따라서 비동기 함수는 언제 실행될지 정확히 예측할 수 없다는 것이다.
그렇다면 어떻게 하면 a = 2를 출력할 수 있을까?
let a = 1;
const callbackHell = (value, callback)=>{
setTimeout(()=>{
a = value;
callback(a)
}, 2000)
}
callbackHell(2, console.log)
위와 같이 예제를 적으면 a=2가 출력되는 것을 알 수 있다. 즉, 일단 setTimeout함수자체를 전역 코드에 심어서 실행되게 끔해버리고, 출력되게 하는 것이다. callback함수로 넣어주어 a를 출력하게끔 만들었다.
그럼 a가 2 가 아니라, 3, 4, 5, 이런식으로 변하고 또 a를 출력하고 싶다면 어떻게 해야할까?
let a = 1;
const callbackHell = (value, callback)=>{
setTimeout(()=>{
a = value;
callback(a)
}, 2000)
}
callbackHell(2, console.log)
callbackHell(3, console.log)
callbackHell(4, console.log)
물론 이런 방법도 있겠지만,
let a = 1;
const callbackHell = (value, callback)=>{
setTimeout(()=>{
a = value;
callback(a)
}, 2000)
}
callbackHell(2, (a)=>{
console.log(a)
callbackHell(3, (a)=>{
console.log(a)
callbackHell(4, console.log)
})
})
콜백함수로 또 콜백을 주고, 하면 이렇게 된다. a를 순차적으로 올라가서 10까지 만들고싶다면?
callbackHell(2, ()=>{
callbackHell(3, ()=>{
callbackHell(4, ()=>{
callbackHell(5, ()=>{
callbackHell(6, ()=>{
...
})
})
})
})
})
very bad.. 그니까 콜백지옥에 빠지는 이유는 비동기함수를 활용해서 변수가 시간이란 개념을 활용해서 순서있게 변하는걸 보여주고싶은데, 그게 불가능하니가 콜백으로 넣어서 변하게 해주고 또변하ㅔㄱ 해주고 .. 이렇게 돼서 콜백지옥에빠지는 것이라고 생각했다.
그것 이외에도 콜백함수때문에 눈까락이 아파지면, 바로 콜백헬임
그럼 이렇게 비동기 함수를 활용해서, 차차 단계로 이동하는 것을 어떻게 쓸 수 있을까? 성질머리가 급해서 한줄씩 개빠르게읽어버리는 콜스택놈이.. 어케 시간을 인지하게끔 만들어서 나중에 실행하게끔 하는걸 어떻게 구현?
const get = url =>{
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = ()=>{
if(xhr.status === 200){
return JSON.parse(xhr.response)
}
else{
console.log(`${xhr.status} ${xhr.statusText}`)
}
}
}
const response = get('https://jsonplaceholder.typicode.com/posts/1')
console.log(response) //undefined
get 함수는 XMLHttpRequest 객체를 열고, HTTP요청을 초기화 한후, 요청을 전송한다. 그리고 요청이 완료되면 그 결과값을 리턴하거나 요청에 오류가 생기면 그 오류를 보여지게끔 만들었다. 하지만, response는 undefined를 출력한다. 분명 요청이 완료되었으면 그 요청해준 값을 리턴해야할텐데?왜?
왜냐면 onload자체도 비동기 함수이기 때문이다. get함수에서 요청까지만 하고 함수를 종료한다. 따라서 onload 이벤트 핸들러의 반환값을 캐치할 수 없게 된다.
결국 위와 같은 이야기를 모두 종합해보면 비동기 함수는 비동기 처리 결과를 외부에 반환할 수도 없고 상위 스코프 변수에도 할당할 수 없다. 따라서 비동기 함수의 처리 결과에 대한 후속 처리는 비동기 함수 내부에서 수행해야한다. 비동기 처리 결과에 대한 후속 처리를 수행하기 위해서는 대부분 콜백함수를 전달하는 것이 일반적이다. 따라서 필요에 따라 비동기 처리가 성공하면 호출될 콜백 함수와 비동기 처리가 실패하면 호출될 콜백 함수를 전달할 수 있다.
// GET 요청을 위한 비동기 함수
const get = (url, successCallback, failureCallback) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 서버의 응답을 콜백 함수에 인수로 전달하면서 호출하여 응답에 대한 후속 처리를 한다.
successCallback(JSON.parse(xhr.response));
} else {
// 에러 정보를 콜백 함수에 인수로 전달하면서 호출하여 에러 처리를 한다.
failureCallback(xhr.status);
}
};
};
// id가 1인 post를 취득
// 서버의 응답에 대한 후속 처리를 위한 콜백 함수를 비동기 함수인 get에 전달해야 한다.
get('https://jsonplaceholder.typicode.com/posts/1', console.log, console.error);
/*
{
"userId": 1,
"id": 1,
"title": "sunt aut facere ...",
"body": "quia et suscipit ..."
}
*/
즉, 비동기함수를 실행하면 그 값을 리턴하게 하지말고 그 값을 콘솔에 보여지게끔 만들어야한다는 것이다. 즉 전역실행컨텍스트 영역으로 넣어버려서 보여지게끔.. 만들어버린다.
예를들어 인스타그램에서 사용자의 아이디를 가져오고, 그 사용자의 팔로잉 목록도 가져오고, 그 사용자의 뭐도 가져오고 뭐도가져오고 이런식으로 뎁스가 있게 데이터를 가져온다고 해보자. 아래와 같은 콜백헬에 빠지게 됨
// GET 요청을 위한 비동기 함수
const get = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if (xhr.status === 200) {
// 서버의 응답을 콜백 함수에 전달하면서 호출하여 응답에 대한 후속 처리를 한다.
callback(JSON.parse(xhr.response));
} else {
console.error(`${xhr.status} ${xhr.statusText}`);
}
};
};
const url = 'https://jsonplaceholder.typicode.com';
// id가 1인 post의 userId를 취득
get(`${url}/posts/1`, ({ userId }) => {
console.log(userId); // 1
// post의 userId를 사용하여 user 정보를 취득
get(`${url}/users/${userId}`, userInfo => {
console.log(userInfo); // {id: 1, name: "Leanne Graham", username: "Bret",...}
});
});
get('/step1', a => {
get(`/step2/${a}`, b => {
get(`/step3/${b}`, c => {
get(`/step4/${c}`, d => {
console.log(d);
});
});
});
});
● 프로미스의 생성
이를 해결하는 방법으로 프로미스가 나오게 된 것이다. 시간을 개념을 활용해 차츰차츰 변해가고, 그 값들을 가져와 후속처리까지 가능한..
Promise 생성자 함수는 비동기 처리를 수행할 콜백 함수(executor 함수) 를 인수로 전달받는데, 이 콜백함수는 resolve, reject 함수를 인수로 받는다. Promise 생성자 함수가 받는 콜백함수 내부에서 비동기 처리를 수행한다. 이때 비동기 처리가 성공하면 resolve 함수를 호출하게 되고, 실패하면 reject함수를 호출한다.
// 프로미스 생성
const promise = new Promise((resolve, reject) => {
// Promise 함수의 콜백 함수 내부에서 비동기 처리를 수행한다.
if (/* 비동기 처리 성공 */) {
resolve('result');
} else { /* 비동기 처리 실패 */
reject('failure reason');
}
});
If else 구분 안하고, resolve, reject 둘다 써버리면 둘다 반환된다이것입니다.
// 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 함수를 호출한다.
resolve(JSON.parse(xhr.response));
} else {
// 에러 처리를 위해 reject 함수를 호출한다.
reject(new Error(xhr.status));
}
};
});
};
// promiseGet 함수는 프로미스를 반환한다.
promiseGet('https://jsonplaceholder.typicode.com/posts/1');
비동기 처리는 Promise 생성자 함수가 인수로 받은 콜백함수 내부에서 실행된다. 비동기 처리가 성공하면 처리 결과를 resolve함수의 인수로 전달하면서 호출하고 처리 결과가 실패하면 reject함수의 인수로 전달하면서 호출한다.
프로미스는 다음과 같이 현재 비동기 처리가 어떻게 진행되고 있는지 나타내는 상태 정보를 갖는다.
프로미스의 상태 정보 | 의미 | 상태 변경 조건 |
pending | 비동기 처리가 수행되지 않은 상태 | 프로미스가 생성된 직후 기본 상태 |
fulfilled | 비동기 처리가 수행된 상태(성공) | resolve 함수 호출 |
rejected | 비동기 처리가 수행된 상태(실패) | reject 함수 호출 |
fulfilled 또는 rejected 상태를 settled 상태라고 한다. settled 상태는 비동기 처리가 수행된 상태이다.(성공이든 실패이든)
// fulfilled된 프로미스
const fulfilled = new Promise(resolve => resolve(1));
// rejected된 프로미스
const rejected = new Promise((_, reject) => reject(new Error('error occurred')));
즉, 프로미스는 비동기 처리 상태와 처리 결과를 관리하는 객체이다.
● 프로미스의 후속 처리 메서드
프로미스의 비동기 처리 상태가 변화하면 이에 따른 후속 처리를 해야한다. 프로미스 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다. 이때 후속 처리 메서드의 콜백 함수에 인수로 프로미스 결과가 전달된다.
모든 후속 처리 메서드는 프로미스를 반환하며 비동기로 동작한다.
⚬ Promise.prototype.then
then 메서드는 두개의 콜백 함수를 인수로 전달받는다.
- fulfilled 상태가 되면 호출된다. 이때 콜백 함수는 프로미스의 비동기 처리 결과를 인수로 전달받는다.
- rejected 상태가 되면 호출된다. 이때 콜백 함수는 프로미스의 비동기 처리 결과를 인수로 전달받는다.
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
resolve('daeun')
}, 2000)
}).then(data=> console.log(data), data=> console.error('not daeun'))
// daeun 출력
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
rejected('daeun')
}, 2000)
}).then(data=> console.log(data), data=> console.error('not daeun'))
// not daeun 출력
⚬ Promise.prototype.catch
catch는 rejected인 경우에만 호출된다.
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
resolve('daeun')
}, 2000)
})
.then(data=> console.log(data))
.catch(data => console.log('catch됏다'))
// 'daeun' 출력됨
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
rejected('daeun')
}, 2000)
})
.then(data=> console.log(data))
.catch(data => console.log('catch됏다'))
// catch 됏다 출력됨
⚬ Promise.prototype.finally
fulfilled, rejected 상태에 상관 없이 무조건 한 번 호출된다.
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
resolve('daeun')
}, 2000)
})
.then(data=> console.log(data))
.catch(data => console.log('catch됏다'))
.finally(()=>{console.log('finally!! 임ㅋ')})
// daeun
// finally!! 임 ㅋ
const stoppromise = new Promise((resolve, rejected)=>{
setTimeout(()=>{
rejected('daeun')
}, 2000)
})
.then(data=> console.log(data))
.catch(data => console.log('catch됏다'))
.finally(()=>{console.log('finally!! 임ㅋ')})
// catch됏다
// finally!! 임ㅋ
● 프로미스의 에러 처리
then 메서드의 두번째 콜백함수를 넣어주면 rejected 일때 호출이 되지만, fulfilled되고 첫번째 콜백함수가 실행된 뒤, 첫번째 콜백함수에서 에러가 뜬다면 그건 두번째 콜백함수가 에러를 잡아줄 수가 없다.
말이 어렵지만 예제를 봐보자.
promiseGet('https://jsonplaceholder.typicode.com/todos/1').then(
res => console.xxx(res),
err => console.error(err)
); // 두 번째 콜백 함수는 첫 번째 콜백 함수에서 발생한 에러를 캐치하지 못한다.
promiseGet('https://jsonplaceholder.typicode.com/todos/1')
.then(res => console.xxx(res))
.catch(err => console.error(err)); // TypeError: console.xxx is not a function
따라서 then, catch메서드를 따로 써주는게 좋다.
● 프로미스 체이닝
then, catch, finally 후속 처리 메서드는 언제나 프로미스를 반환하므로 연속적으로 호출할 수 있다. 이를 프로미스 체이닝이라고 한다.
후속 처리 메서드의 콜백 함수는 프로미스의 비동기 처리 상태가 변경되면 선택적으로 호출된다. 따라서 후속 메서드를 연속적으로 처리하려고 한다면, 무조건적으로 후속 처리 메서드의 콜백함수는 프로미스를 인수로 받아들이고, 프로미스를 리턴한다.
즉, 프로미스 후속 메서드의 콜백 함수가 프로미스가 아닌 값을 반환하더라도, 그 값을 암묵적으로 resolve, reject하여 프로미스를 생성해 반환한다.
따라서 반환값을 명시해줌으로써, 콜백헬이 발생하지 않는 것이다.
아까 앞에서 이쥐롤 났던 것을..
// id가 1인 post의 userId를 취득
get(`${url}/posts/1`, ({ userId }) => {
console.log(userId); // 1
// post의 userId를 사용하여 user 정보를 취득
get(`${url}/users/${userId}`, userInfo => {
console.log(userInfo); // {id: 1, name: "Leanne Graham", username: "Bret",...}
});
});
이로케나 깔끔하게? 정리할 수 있다.
// promiseGet 함수는 프로미스를 반환한다.
const url = 'https://jsonplaceholder.typicode.com/posts/1';
promiseGet(`${url}/posts/1`)
.then(({userId}) => promiseGet(`${url}/users/${userId}`))
.then(userInfo => console.log(userInfo))
.catch(err => console.err(err))
● 프로미스의 정적메서드
⚬ Promise.resolve / Promise.reject
// 배열을 resolve하는 프로미스를 생성
const resolvedPromise = Promise.resolve([1, 2, 3]);
resolvedPromise.then(console.log); // [1, 2, 3]
위의 예제는 아래와 같다.
const resolvedPromise = new Promise(resolve => resolve([1, 2, 3]));
resolvedPromise.then(console.log); // [1, 2, 3]
// 에러 객체를 reject하는 프로미스를 생성
const rejectedPromise = Promise.reject(new Error('Error!'));
rejectedPromise.catch(console.log); // Error: Error!
위는 아래와 같다.
const rejectedPromise = new Promise((_, reject) => reject(new Error('Error!')));
rejectedPromise.catch(console.log); // Error: Error!
⚬ Promise.all
Promise.all은 여러 개의 비동기 처리를 모두 병렬처리한다.
const requestData1 = ()=>
new Promise(resolve => setTimeout(()=> resolve(1), 1000))
const requestData2 = ()=>
new Promise(resolve => setTimeout(()=> resolve(2), 1000))
const requestData3 = ()=>
new Promise(resolve => setTimeout(()=> resolve(3), 1000))
const res = []
requestData1()
.then(data => {
res.push(data);
return requestData2();
})
.then(data =>{
res.push(data);
return requestData3();
})
.then(data =>{
res.push(data);
console.log(res)
})
const requestData1 = ()=>
new Promise(resolve => setTimeout(()=> resolve(1), 1000))
const requestData2 = ()=>
new Promise(resolve => setTimeout(()=> resolve(2), 1000))
const requestData3 = ()=>
new Promise(resolve => setTimeout(()=> resolve(3), 1000))
Promise.all([requestData1(), requestData2(), requestData3()])
.then(console.log)
.catch(console.error)
Promise.all 메서드는 병렬적으로 처리되기 때문에, 처음의 코드처럼 순차적으로 처리하지 않는다. 따라서 가장 늦게 fulfilled 되는 프로미스 처리보다 조금 길게 처리된다.
하지만 세 개 중 하나라도 rejected 상태가 되는 경우, 가장 먼저 rejectede된 프로미스를 기준으로 reject를 호출하고 즉시 종료한다.
const requestData1 = ()=>
new Promise((_, reject) => setTimeout(()=> reject(new Error('Error-1')), 1000))
const requestData2 = ()=>
new Promise((_, reject) => setTimeout(()=> reject(new Error('Error-2')), 2000))
const requestData3 = ()=>
new Promise((_, reject) => setTimeout(()=> reject(new Error('Error-3')), 3000))
Promise.all([requestData1(), requestData2(), requestData3()])
.then(console.log)
.catch(console.error) // Error: Error-1
그리고 Promise.all 메서드는 인수로 전달받은 이터러블 요소가 프로미스가 아닐 경우 Promise.resolve 메서드를 통해 프로미스로 래핑한다.
Promise.all([
1, // => Promise.resolve(1)
2, // => Promise.resolve(2)
3 // => Promise.resolve(3)
])
.then(console.log) // [1, 2, 3]
.catch(console.log);
다음은 깃허브 아이디로 깃허브 사용자 이름을 취득하는 3개의 비동기 처리를 모두 병렬로 처리하는 예제이다.
// 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 함수를 호출한다.
resolve(JSON.parse(xhr.response));
} else {
// 에러 처리를 위해 reject 함수를 호출한다.
reject(new Error(xhr.status));
}
};
});
};
const githubIds = ['jeresig', 'ahejlsberg', 'ungmo2'];
Promise.all(githubIds.map(id => promiseGet(`https://api.github.com/users/${id}`)))
// [Promise, Promise, Promise] => Promise [userInfo, userInfo, userInfo]
.then(users => users.map(user => user.name))
// [userInfo, userInfo, userInfo] => Promise ['John Resig', 'Anders Hejlsberg', 'Ungmo Lee']
.then(console.log)
.catch(console.error);
⚬ Promise.race
Promise.race 메서드는 Promise.all 메서드와 동일하게 프로미스를 요소로 갖는 배열 등의 이터러블을 인수로 전달받는다.
가장 먼저 settled된 상태가 된 프로미스 처리 결과를 반환한다.
Promise.race([
new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3
])
.then(console.log) // 3
.catch(console.log);
Promise.race([
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 1')), 3000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 2')), 2000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error 3')), 1000))
])
.then(console.log)
.catch(console.log); // Error: Error 3
⚬ Promise.allSettled
allSettled 메서드는 모두 settled된 상태가 되면 처리 결과를 배열로 반환한다.
Promise.allSettled([
new Promise(resolve => setTimeout(() => resolve(1), 2000)),
new Promise((_, reject) => setTimeout(() => reject(new Error('Error!')), 1000))
]).then(console.log);
/*
[
{status: "fulfilled", value: 1},
{status: "rejected", reason: Error: Error! at <anonymous>:3:54}
]
*/
● 마이크로태스크 큐
setTimeout(()=>{console.log(1)}, 2000)
Promise.resolve()
.then(()=>{console.log(2)})
.then(()=>{console.log(3)})
위의 코드의 실행 순서를 예상해본다면, 1 > 2 > 3 순으로 출력이 될 것 같아 보이지만 실은 2 > 3 > 1 순으로 출력된다.
왜냐하면 Promise의 콜백함수들은 마이크로 태스크 큐에 저장되기 때문이다. 마이크로태스크 큐는 태스크 큐와 별도의 큐이다. 마이크로태스크 큐에는 프로미스의 후속 처리 메서드의 콜백 함수가 일시 저장된다. 그 외 비동기 함수의 콜백 함수나 이벤트 핸들러는 태스크 큐에 저장된다.
콜백 함수나 이벤트 핸들러를 일시 저장한다는 점에서는 같지만, 태스크 큐와 동일한 마이크로태스크 큐는 태스크큐보다 우선순위가 높다.
즉, 이벤트 루프는 콜 스택이 비면 먼저 마이크로태스크 큐에 대기하고 있는 함수를 가져와 실행하고, 마이크로태스크 큐가 비면 태스크 큐에서 대기하고 있는 함수를 가져와 실행한다.
● fetch
fetch 함수 XMLHttpRequest 객체와 마찬가지로 HTTP 요청 전송 기능을 제공하는 클라이언트 사이드 Web API이다. fetch함수는 XMLHttpRequest 객체보다 사용법이 간단하고 프로미스를 지원하기 때문에 비동기 처리를 위한 콜백 패턴 단점에서 자유롭다.
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(response => console.log(response))
또한 Response.prototype.json 메서드를 사용한다. Response 객체에서 HTTP 응답 몸체를 취득하여 역직렬화한다.
fetch('https://jsonplaceholder.typicode.com/todos/1')
// response는 HTTP 응답을 나타내는 Response 객체이다.
// json 메서드를 사용하여 Response 객체에서 HTTP 응답 몸체를 취득하여 역직렬화한다.
.then(response => response.json())
// json은 역직렬화된 HTTP 응답 몸체이다.
.then(json => console.log(json));
// {userId: 1, id: 1, title: "delectus aut autem", completed: false}
fetch 함수를 사용할 때에는 에러 처리를 주의해야햔다.
const wrongUrl = 'https://jsonplaceholder.typicode.com/XXX/1';
// 부적절한 URL이 지정되었기 때문에 404 Not Found 에러가 발생한다.
fetch(wrongUrl)
.then(()=>console.log('ok'))
.catch(()=> console.log('error'))
URL이 지정되었기 때문에 404 Not Found 에러가 발생하고 catch의 콜백함수가 실행될 것 같지만 'ok'가 출력된다.
fetch 함수가 반환하는 프로미스는 기본적으로 404 Not found 나 HTTP 에러가 발생해도 에러를 reject하지 않고 불리언 타입의 ok 상태를 false로 설정한 Response 객체를 resolve한다. 오프라인 등 네트워크 장애나 CORS 에러에 의해 요청이 완료되지 못한 경우에만 프로미스를 reject한다.
const wrongUrl = 'https://jsonplaceholder.typicode.com/XXX/1';
// 부적절한 URL이 지정되었기 때문에 404 Not Found 에러가 발생한다.
fetch(wrongUrl)
// response는 HTTP 응답을 나타내는 Response 객체다.
.then(response => {
if (!response.ok) throw new Error(response.statusText);
return response.json();
})
.then(todo => console.log(todo))
.catch(err => console.error(err));
참고로 axios는 모든 HTTP 에러를 reject하는 프로미스를 반환한다.
fetch 함수를 통해 HTTP 요청을 하고 싶을 때에는
fetch 함수의 첫번 째 인수는 HTTP 요청을 할 URL, 두번째 인수로는 HTTP요청 메서드, HTTP 요청 헤더, 페이로드 등을 설정한 객체를 전달한다.
const request = {
get(url){
return fetch(url)
},
post(url, payload){
return fetch(url, {
method: "POST",
headers : { 'content-Type' : 'application/json'},
body : JSON.stringify(payload)
})
},
patch(url, payload){
return fetch(url, {
method : "PATCH",
headers : { 'content-Type' : 'application/json'},
body : JSON.stringify(payload)
})
},
delete(url){
return fetch(url, { method : "DELETE"})
}
}