강의 출처 : The Complete JavaScript Course 2022 Jonas (Udemy)
XMLHttpRequest로 사이트 받는 형태
const request = new XMLHttpRequest();
request.open('GET', `https://restcountries.com/v2/name/${country}`);
request.send();
const request = fetch('https://restcountries.com/v2/name/portugal');
console.log(request); // return promise
fetch function은 바로 promise를 return한다는 특징을 가지고 있다.
Promise : Promise 객체는 동기 작업으로 인한 미래의 성공 또는 실패와 그 결과 값을 나타낸다. 프로미스가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자로, 비동기 연산이 종료된 이후에 결과 값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있다. 프로미스를 사용하면 비동기 메서드에서 마치 동기 메서드처럼 값을 반환할 수 있다. 다만 최종 결과를 반환하는 것이 아니고, 미래의 어떤 시점에 결과를 제공하겠다는 '약속'(프로미스)을 반환한다.
간단히 말하면 비동기적으로 전달된 값들을 갖고 있는 container이다. 또는 미래의 값에 대한 container라고 할 수도 있다.
미래의 값(future value) 예시 : Response from AJAX call
처음 response 만들었을 때는 value 가 없지만 나중에 생길 것을 우리는 알 수 있다.
Promise 장점
Promise 에 대한 비유
Promise는 복권과도 같다. 만약 내가 올바른 결과값을 예상한다면 돈을 받을 것이고 그렇지 못한다면 받지 못함. (promise 즉 약속된 것이므로) 복권이 추첨되는 것은 비동기적으로 일어나는 것임.
lottery ticket that I will receive money if I guess correnct outcome
우리가 이미 promise를 가지고 있을때 promise를 이용할 수 있다. ex) promise returned from Fetch API
아래 코드에서 response의 body에 접근하기 위해서는 json을 이용해야한다. json은 또다른 promise를 return한다는 특징을 가지고 있다.
const renderCountry = function (data, className = '') {
const html = ` <article class="country ${className}">
<img class="country__img" src="${data.flag}" />
<div class="country__data">
<h3 class="country__name">${data.name}</h3>
<h4 class="country__region">${data.region}</h4>
<p class="country__row"><span>👫</span>${(
+data.population / 10000000
).toFixed(1)} people</p>
<p class="country__row"><span>🗣️</span>${data.languages[0].name}</p>
<p class="country__row"><span>💰</span>${data.currencies[0].name}</p>
</div>
</article>`;
countriesContainer.insertAdjacentHTML('beforeend', html);
countriesContainer.style.opacity = 1;
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText('beforeend', msg);
countriesContainer.style.opacity = 1;
};
const getCountryData = function (country) {
fetch(`https://restcountries.com/v2/name/${country}`)
.then(response => response.json())
.then(data => renderCountry(data[0]));
};
getCountryData('portugal');
const getCountryData = function (country) {
//country 1
fetch(`https://restcountries.com/v2/name/${country}`)
.then(response => {
//Throwing errors manually
if (!response.ok) throw new Error(`Country not found ${response.status}`);
return response.json();
})
.then(data => {
renderCountry(data[0]);
const neighbor = data[0].borders[0];
if (!neighbor) return;
//country 2
return fetch(`https://restcountries.com/v2/alpha/${neighbor}`);
})
.then(response => {
if (!response.ok) throw new Error(`Country not found ${response.status}`);
return response.json();
})
.then(data => renderCountry(data, 'neighbour'))
.catch(err => {
console.error(`${err}💥💥💥`); //Failed to fetch💥💥💥
renderError(`Something went wrong 💥💥 ${err.message}. Try again!`); //user들도 화면에서 볼 수 있도록
})
.finally(() => {
countriesContainer.style.opacity = 1;
});
};
getJSON이라는 함수를 만들어서 코드를 더욱 간편하게 만들기
const getJSON = function (url, errorMsg = 'Something went wrong') {
return fetch(url).then(response => {
if (!response.ok) throw new Error(`${errorMsg} (${response.status})`);
return response.json();
});
};
const getCountryData = function (country) {
//country 1
getJSON(`https://restcountries.com/v2/name/${country}`, 'Country not found')
.then(data => {
renderCountry(data[0]);
const neighbor = data[0].borders[0];
if (!neighbor) throw new Error('No neighbor found!');
//country 2
return getJSON(
`https://restcountries.com/v2/alpha/${neighbor}`,
'Country not found'
);
})
.then(data => renderCountry(data, 'neighbour'))
.catch(err => {
console.error(`${err}💥💥💥`); //Failed to fetch💥💥💥
renderError(`Something went wrong 💥💥 ${err.message}. Try again!`); //user들도 화면에서 볼 수 있도록
})
.finally(() => {
countriesContainer.style.opacity = 1;
});
};
btn.addEventListener('click', function () {
getCountryData('portugal');
});
getCountryData('dfasdfadsf');
// 찾을 수 없는 값을 넣을 경우 promise는 reject로 인식하지 않는다. promise는 오직 internet connection이 되지 않았을 때만 reject로 인식! error를 undefined (reading 'flag')라고 표현. That's not what we want.
수동적으로 error 처리를 해줄 때에는 throw new Error('')해줄 것!
Runtime in the Browser : 'Container' which includes all the pieces necessary to execute JavaScript code
JS engine : "Heart of the runtime"
Heap : Where object are stored in memory
Call stack : Where code is actually executed -> only ONE thread of execution. No multitasking!
WEB APIs : APIs privided to the engine(JS 내에 있는 것 아님!)
Callback Queue : Ready to be executed callback functions(coming from events)
Concurrency model : How JavaScript handles multiple tasks happening at the same time
el = documemt.querySelector('img');
el.src = 'dog.jpg';
el.addEventListner('load',()=> {
el.classList.add('fadeIn');
});
fetch('http://someurl.com/api')
.then(res => console.log(res))
'dog.jpg'는 call stack(main thread of execution) 내에서 이루지는 것이 아니라 web APIs environment 자체에서 이루어짐. => 비동기적 코드
WEB APIs : 비동기적인 업무가 처리되는 곳
이미지 로딩되는 동안 load event내에서 콜백함수 기다리고 있다. load가 끝나면 callback queue에 콜백함수 추가 된다.
data fetching 되는 동안 then 다음의 콜백함수 web APIs에서 기다리고 있다.
fetch가 끝나면 microtasks queue로 옮겨진다. (callback queue보다 우선순위에 있음. callback queue에 있는 콜백함수들보다 먼저 event loop가 데려간다. )
callback queue 내의 callback 함수는 event loop가 callstack으로 옮길 때까지 기다린다. 콜스택의 함수가 비어지면 바로 발생한다.
요약 : WEB APIs 와 event loop가 블로킹이 되지 않는 비동기적 코드 실행을 가능하게 만든다.
new Promise(executer function)
const lotteryPromise = new Promise(function (resolve, reject) {
console.log('Lottery draw is happening 💎');
setTimeout(function () {
if (Math.random() >= 0.5) {
resolve('You WIN 💰');
} else {
reject(new Error('You lost your money 💩'));
}
}, 2000);
});
lotteryPromise.then(res => console.log(res)).catch(err => console.error(err));
또다른 예시 - setTimeout premise화 하기
const wait = function (seconds) {
return new Promise(function (resolve) {
setTimeout(resolve, seconds * 1000);
});
};
wait(2)
.then(() => {
console.log('1 second passed');
return wait(1);
})
.then(() => {
console.log('2 second passed');
return wait(1);
})
.then(() => {
console.log('3 second passed');
return wait(1);
})
.then(() => {
console.log('4 second passed');
return wait(1);
});
콜백지옥과 비교
setTimeout(() => {
console.log('1 second passed');
setTimeout(() => {
console.log('2 seconds passed');
setTimeout(() => {
console.log('3 seconds passed');
}, 1000);
setTimeout(() => {
console.log('4 seconds passed');
}, 1000);
}, 1000);
}, 1000);
Promise resolve와 reject 함수 바로 만들어 실행하기
Promise.resolve('abc').then(x => console.log(x));
Promise.reject(new Error('Problem!')).catch(x => console.error(x));
const getPosition = function () {
return new Promise(function (resolve, reject) {
// navigator.geolocation.getCurrentPosition(
// position => resolve(position),
// err => reject(err)
// );
navigator.geolocation.getCurrentPosition(resolve, reject);
});
};
getPosition()
.then(pos => console.log(pos))
.catch(err => console.error(err));
const whereAmI = function () {
getPosition()
.then(pos => {
const { latitude: lat, longitude: lng } = pos.coords;
return fetch(`https://geocode.xyz/${lat},${lng}?geoit=json`);
})
.then(response => {
if (!response.ok)
throw new Error(`Problem with geocoding ${response.status}`);
return response.json();
})
.then(data => {
//console.log(data);
const { region, country } = data;
console.log(`You are in ${region}`);
return fetch(`https://restcountries.com/v2/name/${country}`);
})
.then(response => {
if (!response.ok)
throw new Error(`Country not found (${response.status})`);
return response.json();
})
.then(data => renderCountry(data[0]))
.catch(err => console.error(`${err.message} 💥`));
};