Promise
- Это объект с помощью которого мы работаем с отложенными и асинхронными операциями. Этот объект используется в качестве заполнителя для будущего результата асинхронной операциии. Или еще проще, promise
просто контейнер для будущего значения.
С помощью промисов мы работает с ajax
. Нам пришел ответ, от вызова ajax
, этот ответ мы и поместим в promise
.
Для создания промиса используем конструктор new Promise
. Promise принимает в себя функцию с двумя аргументами.
resolve
- эта функция выполнится при удачном выполнении промиса.reject
- эта функция выполнится при неудачном выполнении промиса. При работе с fetch
мы подробнее разберем этот момент.let b = new Promise((resolve,reject)=> {})
Promise может находится в трех состояниях:
pending:
начальное состояни. Ожидание, того когда станет доступно будущее значение.fulfilled:
операция завершена удачно.rejected:
операция завершена с ошибкой.fetch
мы будем только потреблять.Для управления этими состояниями существует метод then
, мы вызываем его прямо у промиса, он доступен для всех промисов.
then
в себя принимает колбек функцию, которая запустится когда промис перейдет в состояние выполнено(fulfilled).
let b = new Promise((resolve,reject)=> {
return resolve('hi') // мы в промисе вернули resolve, а вунтри текст hi. промис выполнился
}).then(hi => console.log(hi)) // hi
// внутри then выполнилась колбек функция которая в свой аргумент приняла то, что вернул промис
Метод catch
будет выполняться при неудачном выполнении кода.
let b = new Promise((resolve,reject)=> {
return resolve('hi')
}).then(hi => bye).catch(()=> { // тут я вернул не hi, а bye
alert('Привет тут ошибка') // произошла ошибка и выполнился catch
})
finally
используется всегда в самом конце после всех then
. Работает и при удачном выполнении и при неудачном.
Например мы можем отчистить все данные после работы промиса и работы с сервером.
let b = new Promise((resolve,reject)=> {
return resolve('hi')
}).then(hi => hi).finally(()=> {
console.log('конец'); // конец
})
Далее расмотрим какие проблемы решает promise
, а после как вообще с ним работать и для чего.
Колбек ад, это когда колбеков очень много, все становится очень запутанно и непонятно.
// имитируем асинхронную работу
console.log('Запрос данных...'); // выводим запрос
setTimeout(()=> {
console.log('Подготовка данных...'); // через 2 сек у нас идет подготовка
const product = { // подготавливаем объект
name:'TV',
price: 3000
}
console.log(product); // выводим объект
setTimeout(()=> {
product.status = 'order'; // добавляем свойство в объект
console.log(product.status); // order типо заказан
setTimeout(()=> {
product.sending = 'sent' // после типо мы отправили и бла бла бла
console.log(product.sending); // sent
},2000) // суть в том, что получается вот такая лесенка, это классический колбек ад
},2000) // если нам нужна будет еще одна операция, у нас появится еще одна такая лесенка
},2000) // представим что их штук 5, вот тут и начинается ад
Что бы у нас наш код не превратился в колбек ад, нам понадабятся promise
.
const rq = new Promise((resolve, reject) => { // создаем промис
console.log('Запрос данных');
setTimeout(() => {
const product = {
name: 'TV',
price: 2000,
bool: true
};
// получается так, что если все удачно прошло и мы получили наш объект product, то выполняется resolve, которая и принимает в себя этот продукт
resolve(product);
}, 3000);
});
rq.then((product) => { // rq - это промис который выполнил свою работу, далее это переходит в then
console.log('Подготовка данных');
return new Promise((resolve, reject) => { // здесь мы можем вернуть еще один промис
setTimeout(() => {
product.status = 'order'; // модифицировать наш продукт
if (product.bool === true) {
resolve(product); // если у нас все четко и в bool: true, то работаем дальше
} else { // иначе ошибка и используем reject
reject();
// само собой, это пример и никто не будет писать такое условие. Подробнее далее в разборе fetch
}
}, 2000)
});
}).then((data) => { // тут опять получаем наш product
data.modify = true; // меняем наш объект
return data; // возвращаем его
}).then((data) => {
console.log(data);
}).catch(() => {
console.log('ОШИБКА');
}).finally(() => {
console.log('Конец');
});
По сути работа с промисами, превращается в такую вот некую цепочку промисов, при работе с fetch
все станет намного яснее.
Сейчас мы воспользуемся API restcountries
- с помощью него мы сможем получать информацию о странах, ну и делать с ними всякое, мм..ага..дада..
const getCountry = (country) => { // Создаем функцию, в нее будем передавать нужную страну
const request = new XMLHttpRequest(); // создаем объект XMLHttpRequest
// говорим, что нам надо, тут же указываем аргументом страну которую хотим
request.open("GET", `https://restcountries.com/v3.1/name/${country}`);
request.send() // отправляем запрос
request.addEventListener('load', () => { // создаем событие load, оно будет срабатывать когда данные будут получены
const data = JSON.parse(request.response); // парсим наш ответ в объект
// у каждой страны язык находится в объекте languages, но имя свойств у всех стран разные
// поэтому мы получаем просто их значения без ключей и все. Будем выводить самое первое значение
const lang = Object.values(data[0].languages)
// создадим переменную с html кодом, который поместим на страницу с версткой
const html = `
<div class="country">
<img class="country-img" src="${data[0].flags.png}" />
<div class="country-data">
<h3 class="country-name">${data[0].name.official}</h3>
<p class="country-lang"><span>Язык: </span>${lang[0]}</p>
</div>
</div>
`
document.querySelector('body').insertAdjacentHTML('afterbegin',html) // помещаем, про этот метод я говорил уже, да да
})
}
getCountry('usa') // вызываем
Вот что мы получаем:
А теперь посмотрим на колбек ад, который может произойти на практике.
const showCountry = (data,className) => { // отдельно помещаем отображение страны
const lang = Object.values(data[0].languages)
const html = `
<div class="country ${className}">
<img class="country__img" src="${data[0].flags.png}" />
<div class="country__data">
<h3 class="country__name">${data[0].name.official}</h3>
<p class="country__row"><span>Язык: </span>${lang[0]}</p>
</div>
</div>
`
document.querySelector('body').insertAdjacentHTML('afterbegin', html)
}
const getCountyAndBorderCountries = (country) => { // теперь мы получаем страну и ее первую соседнюю страну
const request = new XMLHttpRequest();
request.open("GET", `https://restcountries.com/v3.1/name/${country}`); // все так же как и было
request.send()
request.addEventListener('load', () => {
const data = JSON.parse(request.response);
showCountry(data) // теперь тут отображаем страну
const [firstCountry] = data[0].borders; // получаем соседнюю страну
const request2 = new XMLHttpRequest(); // вызываем новый ajax
request2.open("GET", `https://restcountries.com/v3.1/alpha/${firstCountry}`); // теперь запрашиваем соседнюю страну
request2.send() // все как всегда
request2.addEventListener('load', () => { // событие load
const data = JSON.parse(request2.response);
showCountry(data,"neighbour") // и показываем ее с нужным классом
})
})
}
getCountyAndBorderCountries('usa')
Вот что получается:
Вот получается у нас, что в одном ajax
запросе находится еще один, теперь представим, что нам нужно еще узнать соседнюю страну соседней страны, или еще сделать какой то вызовы ajax
.
Вот тогда и будет ад, в одном запросе другой и так далее.
Теперь посмотрим как оно будет с fetch
.
fetch
- это современный способ для работы с сетевыми запросами. Сейчас перенесем пример выше на fetch
.
fetch
не нужно делать промис в ручную, функция fetch
создает промис и возвращает его.Давайте потребим наш промис из функции fetch
.
// кусочек из кода выше
const showCountry = (data,className = "") => {
const lang = Object.values(data[0].languages)
const html = `
<div class="country ${className}">
<img class="country__img" src="${data[0].flags.png}" />
<div class="country__data">
<h3 class="country__name">${data[0].name.official}</h3>
<p class="country__row"><span>Язык: </span>${lang[0]}</p>
</div>
</div>
`
document.querySelector('body').insertAdjacentHTML('afterbegin', html)
}
// используя fetch получим нашу страну
const getCountryData = (country) => {
// Мы просто вызываем fetch и помещаем api
const response = fetch(`https://restcountries.com/v3.1/name/${country}`) // получаем ответ
.then(data => data.json()) // если ответ есть, мы переходим в then, тут получаем данные объектом с помощью json()
.then(data => showCountry(data)) // вызываем функцию показать страну.
}
getCountryData('usa') // вот и все.
Теперь давайте получим и соседа, исправим как раз колбек ад, который у нас начал появляться с XMLHttpRequest
.
const getCountryData = (country) => {
const response = fetch(`https://restcountries.com/v3.1/name/${country}`) // получаем ответ
.then(data => data.json()) // если ответ есть, мы переходим в then, тут получаем данные объектом с помощью json()
.then(data => {
showCountry(data) // внутри then так же отображаем основную страну
const [firstCountry] = data[0].borders; // получаем соседа
// тут важно вернуть промис. Получаем соседа
return fetch(`https://restcountries.com/v3.1/alpha/${firstCountry}`)})
.then(data => data.json()) // тут мы продолжаем работать с промисом
.then(data => showCountry(data, 'neighbour')) // тут только с нужным классом
}
getCountryData('usa')
Очень важный момент:
// в конце получая вторую страну мы могли бы написать так
fetch(`https://restcountries.com/v3.1/alpha/${firstCountry}`)
.then(data => data.json())
.then(data => showCountry(data, 'neighbour'))
// но это большая ошибка, таким образом у нас опять получится callback hell
// у нас одна колбек функция определена внутри другой колбек функции, внешней, то есть первым вызывом нашей главной страны
// получается начиная опять внутри какой то вызов, у нас будет колбек ад
Теперь еще раз посмотирм на правильный вариант, так станет понятнее.
.then(data => {
showCountry(data)
const [firstCountry] = data[0].borders; /
return fetch(`https://restcountries.com/v3.1/alpha/${firstCountry}`) }) // тут заканчивается then который возвращает нам промис
.then(data => data.json()) // и далее идут другие then
.then(data => showCountry(data, 'neighbour')) // мы не вызываем их внутри еще одного fetch, мы вызываем их лишь после другого then
// таким образом у нас нет колбек ада
В общем и все, получаем тот же результат:
До этого мы конечно все сделали красиво, но мы не обработали никаких ошибок, а если сервер не ответит? Если ничего не придет? Давайте исправим и это.
Будем использовать уже знакомые catch
.
В каком случае мы можем не получить наш промис?.Скорее всего, если у нас не будет интернета.
В инспекторе кода на вкладке сеть
& network
можно открыть такое окошко:
Для отлова ошибки о дисконекте используем кнопку, при нажатии который мы будем получать наши страны.
const btn = document.querySelector('.btn'); // обычная кнопка с классом btn
btn.addEventListener('click',() => { // получаем страны при нажатии
getCountryData('usa');
} )
После мы открываем страницу, где будет одна кнопка, в инспекторе кода в сети ставим офлайн и нажимаем кнопку, таким образом мы получим 2 ошибки.
Если у вас появляются страны даже в офлайн режиме, то почистите кеш браузера. А после, рядом где ставили офлайн режим поставьте галочку "Отключить кеш".
Давайте теперь для отлова ошибки используем вторую колбек функцию, которая в обычном промисе называется reject
. Называть ее конечно же, можно как угодно.
// начало кода
const getCountryData = (country) => {
const response = fetch(`https://restcountries.com/v3.1/name/${country}`)// получаем страну
.then(data => data.json(), error => { // и тут добавляем вторую функцию
console.log(error); // она выведет нам саму ошибку
})
}
Таким образом, если мы нажмем на нашу кнопку, для показа стран, то вторая ошибка, которая говорит, что мы не нашли промис,
попадет во вторую нашу функцию и выведет ее в alert
. Но если нужно отловить ошибку для следущего вызова, то придется опять писать функцию.
Поэтому давайте вспомним про метод catch
.
Повторим функцию getCountryData
.
const getCountryData = (country) => {
const response = fetch(`https://restcountries.com/v3.1/name/${country}`)
.then(data => data.json())
.then(data => {
showCountry(data)
const [firstCountry] = data[0].borders;
return fetch(`https://restcountries.com/v3.1/alpha/${firstCountry}`) })
.then(data => data.json())
.then(data => showCountry(data, 'neighbour'))
.catch((error)=> { // В конце ставим catch
alert(error) // и так же выводим нашу ошибку.
})
}
А тут мы повторим тот же калькулятор, что и в примере с XMLHttpRequest
:
const inputRub = document.querySelector('#rub'), // получаем элементы
inputUsd = document.querySelector('#usd');
inputRub.addEventListener('input', async () => { // чешаем событие на инпут
let response = await fetch('./js/current.json', { // получаем ответ в переменную response
method: "GET", // это тоже можно не указывать
headers: { // вообще можно написать в одну строчку без method, headers.
'Content-type': 'application/json; charset=utf-8'
},
})
// свойство ok Будет true если status в диапозоне от 200 до 299
if(response.ok) {
let data = await response.json() // парсим наш ответ в объект
inputUsd.value = (+inputRub.value / data.current.usd).toFixed(2) // работаем с инпутами как и было до этого.
} else {
alert(`Ошибка запроса ${response.status}`) // Окно об ошибке
}
})
Вот и все. Потыкать сам калькулятор можно тут . Можете все повторить из предыдущей темы и потом просто вставить этот код, все будет работать так же.
02.12.2022