Качество на RuTube очень паршивое, если не сможете посмотреть оригинал на YouTube, то можете скачать видео с помощью какого-нибудь сервиса.
Содержание урока:
Спойлер с новой задачей (шаблон письма) не делаем, это просто презентация на будущее
В этом видео много раз переделывается одно и то же. Не спешите копипастить, постарайтесь понять что происходит.
Создаём в каталоге src файл api.js
Выносим в него функцию получения валюты
Ключевое слово export перед переменной или функцией говорит, что эта переменная (или функция) будет доступна для импорта в других модулях
Синтаксис с промисами я уже описывал в прошлых лекциях. Вы должны понимать, что этот метод вернёт Promise, т.е. вызывающая функция должна либо использовать асинхронный вызов await, либо метод .then
const API_KEY = 'ce3fd966e7a1d10d65f907b20bf000552158fd3ed1bd614110baa0ac6cb57a7e'
export const loadTiker = tickerName => fetch(
`https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
).then(r => r.json())
Не очень похоже на функцию? На самом деле это один из вариантов объявления лямбда функции.
переменная = (параметры функции) => { тело функции }
Просто тут сделано два упрощения:
Т.е. более привычный способ записи был бы такой:
export const loadTiker = (tickerName) => {
return fetch(
`https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
).then(r => r.json())
}
а если совсем боитесь (не понимаете) лямбда функций, то такой:
export function loadTiker (tickerName) {
return fetch(
`https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
).then(r => r.json())
}
В основном файле приложения (App.vue) прописываем импорт новой функции и меняем код в том месте, где был запрос валюты
импорт
import { loadTiker } from './api'
Фигурные скобки означают декомпозицию, т.е. имя переменной loadTiker доступно в модуле напрямую.
Все экспорты модуля снаружи выглядят как объект и, если не использовать декомпозицию, то для обращения к функции пришлось бы использовать свойство объекта. Например
import someModule from './api' ... someModule.loadTiker
замена кода
setInterval(async () => {
const data = await loadTiker(tickerName)
// тут удаляем старый код запроса данных
Весь текст не расшифровываю - смотрите видео
Смысл в том, что в текущей реализации мы при добавлении новой валюты запускаем таймер с получением данных. При этом у нас нет способов отключить таймер. Получается, что если 10 раз добавить/удалить одну и ту же валюту, то у нас получится 10 одинаковых таймеров при пустом списке валют (это конечно маловероятный случай, но наглядно показывающий ущербность текущей реализации)
К счастью АПИ позволяет получить обратные курсы валют. Не <валюта> к доллару, как сейчас, а доллар к списку валют. Т.е. мы одним выстрелом убиваем двух зайцев: минимизируем количество запросов (в этом варианте одним запросом можно запросить до 500 валют) и избавляемся от необходимости удалять таймеры.
Переписываем функцию loadTiker
export const loadTiker = tickers => fetch(
`https://min-api.cryptocompare.com/data/price?fsym=USD&tsyms=${tickers.join(',')}&api_key=${API_KEY}`
).then(r => r.json())
Обратите внимание:
fsym поменяли на USD, а tsyms на список валют (функция join преобразует массив в строку, разделенную запятыми)В основном компоненте (App.vue) реализуем функцию updateTickers
Эта функция вызывает функцию получения данных о курсах валют и для каждой валюты в списке валют обновляет её стоимость, учитывая возможность отсутствия валюты в результате
async function updateTickers () {
// если список валют пуст, то ничего не делаем
if (!tickers.value.length) return
// получаем данные о валютах
// в параметрах передаём список валют
const data = await loadTiker(
tickers.value.map(
t => t.name
)
)
tickers.value.forEach(t => {
// в верхний регистр переводить не надо,
// мы уже делали самостоятельную работу с валидацией
// и в списке у нас валюты должны быть в верхнем регистре
if (!data[t.name]) {
t.price = '-'
return
}
t.price = 1 / data[t.name]
})
}
Удаляем из метода add получение данных (таймер)
В методе жизненного цикла onBeforeMount переписываем создание таймера для периодического получения данных (хотя можно и непосредственно в script setup)
onBeforeMount(() => {
const tickersData = localStorage.getItem('cryptonomicon-list') ?? '[]'
if (tickersData) {
tickers.value = JSON.parse(tickersData)
}
setInterval(() => {
updateTickers()
}, 5000)
})
Обратите внимание, в видео описан вариант без обёртки в лямбда-функцию, но у меня так не заработало. Используем "классический" вариант.
В массиве валют стоимость теперь хранится в исходном виде (число с кучей цифр после запятой)
Реализуем метод форматирования цены:
function formatPrice (price) {
if (typeof price == 'number') {
return price > 1 ? price.toFixed(2) : price.toPrecision(2)
}
return price
}
У меня иногда появлялось исключение типа "метод toFixed не определён", возможно при значении
'-', поэтому я добавил проверку на тип данных
И вставляем его в шаблон, в то место, где выводится стоимость валюты (самостоятельно)
В рамках отделения бизнес-логики от реализации перенесём обратное преобразование цены в код метода loadTickers (добавили множественное число)
Добавляем действие в цепочку:
export const loadTikers = tickers => fetch(
`https://min-api.cryptocompare.com/data/price?fsym=USD&tsyms=${tickers.join(',')}&api_key=${API_KEY}`
).then(
r => r.json()
).then(rawData => {
return Object.fromEntries(
Object.entries(rawData)
.map(([key,value]) => [key, 1 / value])
)
})
Более подробно о преобразованиях:
{a: 1, b: 2}[['a', 1], ['b', 2]][['a', 1], ['b', 0.5]]{a: 1, b: 0.5}Теперь в методе updateTickers мы можем выкинуть лишнюю логику и оставить только присваивание:
const data = await loadTikers(
tickers.value.map(
t => t.name
)
)
tickers.value.forEach(t => {
t.price = data[t.name] ?? '-'
})
Кто-то нашёл, что апи поддерживает и прямой запрос список-список
Мы меняем endpoint на pricemulti, в fsyms передаём список валют, а в tsyms USD (тоже список, но из одной валюты)
export const loadTikers = tickers => fetch(
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${tickers.join(',')}&tsyms=USD&api_key=${API_KEY}`
).then(r => r.json()).then(rawData => {
return Object.fromEntries(
Object.entries(rawData)
.map(([key,value]) => [key, value.USD])
)
})
Т.к. формат ответа отлчается, то немного правим действие в методе map. Value у нас теперь не обратная цена, а объект с названиями валют и их ценами.
Обратите внимание, мы правили только слой апи, а слой бизнес логики не меняли, но всё продолжает работать. В этом и есть цель разделения бизнес-логики и апи.
На мой взгляд, для junior-ов этого уже достаточно.
Исходный курс рассчитан на middl-ов, если кому-то интересно, то самостоятельно реализуйте задачу с подпиской на стоимость (за дополнительную оценку)