diff --git a/front/data-manager.js b/front/data-manager.js index 8a961bb..54d991d 100644 --- a/front/data-manager.js +++ b/front/data-manager.js @@ -10,6 +10,17 @@ const db_get_filepath = '../db_get.php' /** Дефолтный срок годности кэша : 2 часа */ const CACHE_DEFAULT_TTL_MS = 2 * 60 * 60 * 1000; +/** + * Получить объект кэша, если поддерживается Cache API + * @returns {Promise} + */ +async function getCache() { + if (typeof caches !== 'undefined') { + return await caches.open('cache'); + } + return null; +} + /** * Излечь данные из ответа * @param {string} type - тип извлекаемых данных @@ -19,13 +30,13 @@ const CACHE_DEFAULT_TTL_MS = 2 * 60 * 60 * 1000; const extract_file = recovery(async (type, response) => { switch(type) { case 'file': - return await response.blob() + return await response.blob(); case 'json': - return await response.json() + return await response.json(); default: - throw new Error('Не определен type') + throw new Error('Не определен type'); } -}, (...e) => {throw Error(`Ошибки при извлечении (extract_file): ${e.join(', ')}`)} ) +}, (...e) => {throw Error(`Ошибки при извлечении (extract_file): ${e.join(', ')}`)} ); /** * @param {string} path - путь (либо локальный или https) @@ -33,97 +44,88 @@ const extract_file = recovery(async (type, response) => { * @param {int} timeout - секундомер */ const fetch_with_timeout = recovery(async (path, options = {}, timeout = 10 * 1000) => { - /** Штука для преждевременного завершения fetch */ - const controller = new AbortController() - /** Как я понял, якорь для fetch */ - const signal = controller.signal + const controller = new AbortController(); + const signal = controller.signal; - /** Сработает завершение после истечения timeout */ - const timeoutId = setTimeout(() => controller.abort(), timeout) - /** Запрос с привязкой якоря */ - const response = await fetch(path, {...options, signal}) + const timeoutId = setTimeout(() => controller.abort(), timeout); + const response = await fetch(path, {...options, signal}); - try {return response} - finally {clearTimeout(timeoutId)} + try { return response; } + finally { clearTimeout(timeoutId); } }, (e) => { if(e.name === 'AbortError') - console.log('Запрос прерван по таймауту:', e) - else console.log(e) -}, (...e) => {throw Error(`Ошибки при запросе с таймером (fetch_with_timeout): ${e.join(', ')}`)} ) + console.log('Запрос прерван по таймауту:', e); + else console.log(e); +}, (...e) => {throw Error(`Ошибки при запросе с таймером (fetch_with_timeout): ${e.join(', ')}`)} ); /** - * Вернуть данные из ответа, кэшируя перед этим + * Вернуть данные из ответа, кэшируя перед этим (если Cache API есть) * @param {string} type - тип извлекаемых данных * @param {string} path - путь (либо локальный или https) + * @param {string} cache_key - ключ кэша * @param {Object} body_req - тело запроса * @returns {Promise} */ const return_fetch = recovery(async (type, path, cache_key, body_req) => { - const cache = await caches.open('cache') + const cache = await getCache(); - /** Запрос по пути (если есть тело, то локальное обращение к файлу) */ - const response = !body_req ? await fetch_with_timeout(path) : await fetch_with_timeout(path, { - method: 'POST', - headers: { 'Content-Type': 'application/json'}, - body: JSON.stringify(body_req) - }) + const response = !body_req + ? await fetch_with_timeout(path) + : await fetch_with_timeout(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body_req) + }); - /** Клонирование, т.к blob/json необратимый процесс. */ - const cloned = response.clone() - const blob = await cloned.blob() + if (cache) { + const cloned = response.clone(); + const blob = await cloned.blob(); - const headers = new Headers(cloned.headers) - /** Добавление даты создания в заголовок 'date' кешируемого ответа. */ - headers.set('date', new Date().toUTCString()) + const headers = new Headers(cloned.headers); + headers.set('date', new Date().toUTCString()); - /** Процесс кэширования */ - await cache.put( - cache_key, - new Response(blob, { - status: cloned.status, - statusText: cloned.statusText, - headers - }) - ) - - /** Возвращаем оригинальный (первый) ответ */ - return await extract_file(type, response) -}, (...e) => {throw Error(`Ошибки при запросе и кэшировании (return_fetch): ${e.join(', ')}`)} ) + await cache.put( + cache_key, + new Response(blob, { + status: cloned.status, + statusText: cloned.statusText, + headers + }) + ); + } + return await extract_file(type, response); +}, (...e) => {throw Error(`Ошибки при запросе и кэшировании (return_fetch): ${e.join(', ')}`)} ); /** - * Получить данные из пути(локальный или https) - * @param {*} type - тип извлекаемых данных - * @param {*} path - путь (либо локальный или https) - * @param {*} body_req - тело запроса + * Получить данные из пути (локальный или https), с поддержкой кэша, если он есть + * @param {string} type - тип извлекаемых данных + * @param {string} path - путь (либо локальный или https) + * @param {Object|null} body_req - тело запроса * @returns {Promise} */ const get = recovery(async (type, path, body_req = null) => { - const cache = await caches.open('cache') + const cache = await getCache(); - /** Ключ для добавления или получения кэш-данных. */ - const cache_key = `${path}?type=${type}&file=${encodeURIComponent(JSON.stringify(body_req || {}))}` + const cache_key = `${path}?type=${type}&file=${encodeURIComponent(JSON.stringify(body_req || {}))}`; - /** Результат поиска ответа в кэше */ - const cached_response = await cache.match(cache_key) + if (cache) { + const cached_response = await cache.match(cache_key); - if (cached_response) { - /** Срок годности кэша (если задан) */ - const expires_value = cached_response.headers.get('expires') - /** Дата создания кэша */ - const date_value = cached_response.headers.get('date') - /** До какого числа годен */ - const exp = expires_value && expires_value !== '-1' - ? Date.parse(expires_value) - : Date.parse(date_value ?? '') + CACHE_DEFAULT_TTL_MS - /** Если exp больше, вернуть ответ из кэша, иначе удалить протухшие данные */ - if (Date.now() < exp) { - return await extract_file(type, cached_response ) + if (cached_response) { + const expires_value = cached_response.headers.get('expires'); + const date_value = cached_response.headers.get('date'); + const exp = expires_value && expires_value !== '-1' + ? Date.parse(expires_value) + : Date.parse(date_value ?? '') + CACHE_DEFAULT_TTL_MS; + + if (Date.now() < exp) { + return await extract_file(type, cached_response); + } + await cache.delete(cache_key); } - await cache.delete(cache_key) } - /** Формирование нового кэша, т.к предыдущий протух или его нет */ - return await return_fetch(type, path, cache_key, body_req) -}, (...e) => {throw Error(`Ошибки при получении (get): ${e.join(', ')}`)} ) + return await return_fetch(type, path, cache_key, body_req); +}, (...e) => {throw Error(`Ошибки при получении (get): ${e.join(', ')}`)} ); /** * Получить данные на диске (абсолютный/относительный путь) @@ -134,8 +136,8 @@ const get_local = recovery(async (filepath) => { return await get('file', db_get_filepath, { mode: 'file', path: filepath - }) -}, (...e) => {throw Error(`Ошибки при локальном получении (get_local): ${e.join(', ')}`)} ) + }); +}, (...e) => {throw Error(`Ошибки при локальном получении (get_local): ${e.join(', ')}`)} ); /** * Получить данные без кэширования @@ -144,46 +146,43 @@ const get_local = recovery(async (filepath) => { * @returns {Promise} */ const get_no_cached = recovery(async (type, path) => { - const file = await fetch_with_timeout(path) - return await extract_file(type, file) -}, (...e) => {throw Error(`Ошибки при получении без кэширования (get_no_cached): ${e.join(', ')}`)} ) + const file = await fetch_with_timeout(path); + return await extract_file(type, file); +}, (...e) => {throw Error(`Ошибки при получении без кэширования (get_no_cached): ${e.join(', ')}`)} ); /** - * Получить метериалы поселения для инфо. страницы - * @param {MarkerData} properties - * - нужные свойства (пути) из свойств feature. + * Получить материалы поселения для инфо. страницы + * @param {MarkerData} properties * @returns {Promise<{ background: Blob, images: Blob[], slider: Blob[]}>} */ const get_for_info_page = recovery(async (properties) => { - /** Фон инфо. страницы */ - const background = await get_local(properties.background) + const background = await get_local(properties.background); - /** Фотографи инфо. страницы */ const images = await Promise.all( (properties.images || []).map(i_path => get_local(i_path)) - ) + ); - /** Слайды инфо. страницы (если есть) */ const slider = Array.isArray(properties.slider) ? await Promise.all(properties.slider.map(s_path => get_local(s_path))) - : [] + : []; return { background, images, slider, - } -}, (...e) => {throw Error(`Ошибки при получении материалов для страницы (get_for_info_page): ${e.join(', ')}`)} ) + }; +}, (...e) => {throw Error(`Ошибки при получении материалов для страницы (get_for_info_page): ${e.join(', ')}`)} ); /** * Получить иконки для стилизации страниц. * @returns {Promise} */ -const get_icons = recovery(async () => await get_no_cached ('json', './data/icons.json') -, (...e) => {throw Error(`Ошибки при получении данных для стилизации (get_icons): ${e.join(', ')}`)} ) +const get_icons = recovery(async () => await get_no_cached('json', './data/icons.json') +, (...e) => {throw Error(`Ошибки при получении данных для стилизации (get_icons): ${e.join(', ')}`)} ); + /** - * Получить все geojson'ы из базы данных + * Получить все geojson'ы из базы данных * @returns {Promise} */ const get_all_geojsons = recovery(async () => await get('json', db_get_filepath, {'mode': 'geojsons'}) -, (...e) => {throw Error(`Ошибки при получении geojson'ов из бд (get_all_geojsons): ${e.join(', ')}`)} ) \ No newline at end of file +, (...e) => {throw Error(`Ошибки при получении geojson'ов из бд (get_all_geojsons): ${e.join(', ')}`)} );