Попытка предостеречься от caches (ИИ)
Ano Mr Site / Build and deploy (push) Successful in 9s Details

This commit is contained in:
Red 2025-09-10 21:40:02 +05:00
parent 48f8000258
commit 1a67b16153
1 changed files with 88 additions and 89 deletions

View File

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