Fetch API is an asynchronous web API that comes with native JavaScript,
and it returns the data in the form of promises.
자바스크립트에서는 fetch 로 리소스에 대한 비동기 요청을 할 수 있다. (주로 API를 호출하고 응답 데이터를 받아오는 데에 사용)
서버를 실행한 상태에서는 다음 URL을 통해 정보를 얻어올 수 있다.
fetch를 이용해서 http://localhost:4999/data/latestNews 요청과 http://localhost:4999/data/weather 요청의 결과를 하나의 객체로 합치는 것이 목표이다.
[JS] fetch를 이용한 네트워크 요청 - 1/2 (개념) 참고
* fetch API 는 프로미스 객체를 반환한다. 따라서 then 메서드로 성공 시, 원하는 데이터를 비동기 방식으로 받아온다.
fetch('http://example.com/movies.json') .then((response) => response.json()) .then((data) => console.log(data));
위 코드는 네트워크에서 JSON 파일을 가져와서 콘솔에 출력한다. 가장 단순한 형태의 fetch()는 가져오고자 하는 리소스의 경로를 나타내는 하나의 인수만 받는다. 응답은 Response 객체로 표현되며, 직접 JSON 응답 본문을 받을 수는 없다.
Response (en-US) 객체 역시 JSON 응답 본문을 그대로 포함하지는 않는다. Response는 HTTP 응답 전체를 나타내는 객체로, JSON 본문 콘텐츠를 추출하기 위해서는 json() (en-US) 메서드를 호출해야 한다. json()은 응답 본문 텍스트를 JSON으로 파싱한 결과로 이행하는, 또 다른 프로미스를 반환한다.
위에서 json()은 왜 JSON으로 파싱한 결과로 이행하는, 또 다른 프로미스를 반환할까?.
1. 우리가 받은 Response 객체는 일부만 온 것이다. 아직 데이터를 받는 중 이기 때문에 Promise를 반환한다.
2. 다 받고 난 뒤에 온전한 Response 객체에서 다음 작업을 한다.
** 다른 방식으로는 await으로 기다린 이후에 .json()을 출력한다. (해당 포스트 async & await 파트에 예시 코드가 있다)
https://stackoverflow.com/questions/37555031/why-does-json-return-a-promise
basic chaining
var newsURL = 'http://localhost:4999/data/latestNews';
var weatherURL = 'http://localhost:4999/data/weather';
function getNewsAndWeather() {
return fetch(newsURL) // fetch는 특정 URL로부터 정보를 받아온다
.then(resp => resp.json()) // then은 response 객체를 인자로 받는 콜백을 실행시킨다.
// JSON 본문 콘텐츠를 추출 + 응답 본문 텍스트를 JSON으로 파싱한 결과로 이행하는, 또 다른 프로미스를 반환
.then(json1 => { //json1은 JSON으로 파싱된 데이터
return fetch(weatherURL)
.then(resp => resp.json())
.then(json2 => {
return {
news: json1.data,
weather: json2
}
});
})
}
if (typeof window === 'undefined') { // (설명1)
module.exports = {
getNewsAndWeather
}
}
(설명1)
if(typeof window == 'undefined') 의 의미
1. Code that runs without 'onclick'.
2. Ensure call to server-side function with no browser errors
-> react에서 서버사이드 렌더링(next js 프레임워크)을 할때에 웹 페이지를 렌더링 할때에 초기에는 window나 document 전역객체는 선언되지 않아서 해당 변수를 참조할수 없다. window 전역객체가 참조되지 않을 경우 "undefined"를 반환한다.
// 이렇게도 쓸 수 있다.
function getNewsAndWeather() {
let result = {};
return fetch(newsURL)
.then(resp => resp.json())
.then((json1) => {
result.news = json1.data // 빈 배열 result에 news 속성으로 담아준다
return fetch(weatherURL)
})
.then(resp => resp.json())
.then((json2) => {
result.weather = json2 //위의 내용 반복
return result; // result 리턴
})
}
콘솔로 정리
(1) fetch API로 특정 URL로부터의 데이터를 response 형태로 받아옴. (news 참조)
(2) response.json()을 통해 JSON 본문 콘텐츠 추출
(3) news.data를 이용해 원하는 부분 추출
(궁금) 마지막 return 전에 console.log(result)를 했는데, 왜 콘솔에 4번씩 찍힐까?
Promise.all
function getNewsAndWeatherAll() {
return Promise.all([
fetch(newsURL),
fetch(weatherURL)
])
.then(([newsResponse, weatherResponse]) => { //JSON 파싱을 해줘야 함
return Promise.all([newsResponse.json(), weatherResponse.json()]) //둘다 프로미스이기 때문에 Promise.all
})
.then(([json1, json2]) => {
return {
news: json1.data,
weather: json2
}
})
}
if (typeof window === 'undefined') {
module.exports = {
getNewsAndWeatherAll
}
}
// 이렇게도 가능
function getNewsAndWeatherAll() {
let result = {}; // 빈 객체 선언
const news = fetch(newsURL).then(resp => resp.json()) // json메서드는 프로미스를 또 반환
const weather = fetch(weatherURL).then(resp => resp.json())
return Promise.all([news, json2])
.then(([json1, json2]) => {
result.news = json1.data
result.weather = json2
return result;
})
}
async & await
async function getNewsAndWeatherAsync() {
// await는 json()이 완전한 response 객체로 전달할 때까지 기다린다. 완료가 되면 json1, json2에 들어간다.
let json1 = await fetch(newsURL).then(resp => resp.json());
let json2 = await fetch(weatherURL).then(resp => resp.json());
return {
news: json1.data,
weather: json2
}
}
if (typeof window === 'undefined') {
module.exports = {
getNewsAndWeatherAsync
}
}
꼭 기억하자!
프라미스 체이닝이 가능한 이유는 promise.then을 호출하면 (1) 프라미스가 반환되기 때문이다. 반환된 프라미스엔 당연히 .then을 호출할 수 있다. .then(handler)에 사용된 핸들러가 프라미스를 생성하거나 반환하는 경우 이어지는 핸들러는 프라미스가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받는다.
한편 핸들러가 (2) 값을 반환할 때엔 이 값이 프라미스의 result가 된다. 따라서 다음 .then은 이 값을 이용해 호출된다.
MDN 요약
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Promise.prototype.then()
var p = new Promise(function(resolve, reject) { resolve("성공!"); // 또는 reject("오류!"); }); // then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받는다. // 하나는 Promise가 이행했을 때, 다른 하나는 거부했을 때를 위한 콜백 함수이다. p.then(onFulfilled, onRejected); p.then(function(value) { // 이행 }, function(reason) { // 거부 -> 보통 catch로 처리 });
Promise가 이행하거나 거부했을 때, 각각에 해당하는 핸들러 함수(onFulfilled나 onRejected)가 비동기적으로 실행된다. 핸들러 함수는 다음 규칙을 따른다.
* 함수가 값을 반환할 경우, then에서 반환한 프로미스는 그 반환값을 자신의 결과값으로 하여 이행한다.
* 값을 반환하지 않을 경우, then에서 반환한 프로미스는 undefined를 결과값으로 하여 이행한다.
* 오류가 발생할 경우, then에서 반환한 프로미스는 그 오류를 자신의 결과값으로 하여 거부한다.
* 이미 이행한 프로미스를 반환할 경우, then에서 반환한 프로미스는 그 프로미스의 결과값을 자신의 결과값으로 하여 이행한다.
* 이미 거부한 프로미스를 반환할 경우, then에서 반환한 프로미스는 그 프로미스의 결과값을 자신의 결과값으로 하여 거부한다.
* 대기 중인 프로미스를 반환할 경우, then에서 반환한 프로미스는 그 프로미스의 이행 여부와 결과값을 따른다.
// 이행한 프로미스를 받으면 'then' 블록도 바로 실행되지만,
// 핸들러는 아래 console.log에서와 같이 비동기적으로 실행됨
const resolvedProm = Promise.resolve(33);
let thenProm = resolvedProm.then(value => {
console.log("이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 " + value + "입니다.");
return value;
});
// thenProm의 값을 즉시 기록
console.log(thenProm);
// setTimeout으로 함수 실행을 호출 스택이 빌 때까지 미룰 수 있음 ****
setTimeout(() => {
console.log(thenProm);
});
// 로그 출력 결과 (순서대로):
// Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}
// "이 부분은 호출 스택 이후에 실행됩니다. 전달받은 값이자 반환값은 33입니다."
// Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: 33}
Promise.resolve('foo')
// 1. "foo"를 받고 "bar"를 추가한 다음 그 값으로 이행하여 다음 then에 넘겨줌
.then(function(string) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
string += 'bar';
resolve(string);
}, 1);
});
})
// 2. "foobar"를 받고 그대로 다음 then에 넘겨준 뒤,
// 나중에 콜백 함수에서 가공하고 콘솔에 출력
.then(function(string) {
setTimeout(function() {
string += 'baz';
console.log(string);
}, 1)
return string;
})
// 3. 이 부분의 코드는 이전의 then 블록 안의 (가짜) 비동기 코드에서
// 실제로 문자열을 가공하기 전에 실행됨
.then(function(string) {
console.log("마지막 Then: 앗... 방금 then에서 프로미스 만들고 반환하는 걸 까먹어서 " +
"출력 순서가 좀 이상할지도 몰라요");
// 'baz' 부분은 setTimeout 함수로 비동기적으로 실행되기 때문에
// 이곳의 string에는 아직 'baz' 부분이 없음
console.log(string);
});
// 로그 출력 결과 (순서대로):
// 마지막 Then: 앗... 방금 then에서 프로미스 만들고 반환하는 걸 까먹어서 출력 순서가 좀 이상할지도 몰라요
// foobar
// foobarbaz
체이닝을 이용해 프로미스 기반 함수 위에 다른 프로미스 기반 함수를 구현할 수도 있다.
function fetch_current_data() {
// fetch() API는 프로미스를 반환한다. 이 함수도 API가 비슷하지만,
// 이 함수의 프로미스는 추가 작업을 거친 이후에 이행값을 반환한다.
return fetch('current-data.json').then(response => {
if (response.headers.get('content-type') != 'application/json') {
throw new TypeError();
}
var j = response.json();
// j 가공하기
return j; // fetch_current_data().then()을 통해
// 이행값을 사용할 수 있음
});
}
하하 어제 맘껏 고민하고 맘껏 찾아보고 겨우 이해했던 개념들이 MDN에 땋! 있네 허허
공식 문서 읽는 것을 생활화하자!!!
'FE > JavaScript' 카테고리의 다른 글
[JS] JSON 개요 (JavaScript Object Notation) (0) | 2023.04.12 |
---|---|
[JS] 재귀 (0) | 2023.04.11 |
[JS] Node.js 내장 모듈인 fs 모듈로 비동기 실습 - 2/2 (활용) (0) | 2023.03.26 |
[JS] 비동기 흐름 : 배열 메서드 구현해보기 (Underbar) - 중 (0) | 2023.03.25 |
[JS] fetch를 이용한 네트워크 요청 - 1/2 (개념) (0) | 2023.03.21 |