В OpenLayers загрузка данных карты строится вокруг источников
(ol/source/*) и соответствующих загрузчиков: тайловых,
растровых изображений и векторных данных. Любой сетевой сбой при
загрузке ресурса приводит к ошибке на уровне конкретного элемента
(тайла, изображения или набора объектов), но не останавливает работу
всего слоя. Это создаёт основу для реализации механизма повторных
попыток (retry), который чаще всего внедряется на уровне
пользовательской логики поверх стандартных API.
Ключевой особенностью является отсутствие универсального встроенного retry-механизма: поведение повторных попыток реализуется через переопределение функций загрузки или обработку событий ошибок.
Тайловые источники (ol/source/XYZ,
ol/source/OSM, ol/source/TileImage) используют
загрузку по URL, формируемому шаблоном. Каждый тайл загружается
независимо, и ошибка сети приводит только к его повторной отрисовке
состояния ошибки.
Механизм retry реализуется через:
tileLoadFunctiontileloaderrorsrc у тайлаБазовая логика загрузки выглядит как установка URL:
tile.setImage(new Image());
tile.getImage().src = url;
При этом OpenLayers не ограничивает количество повторных попыток, если логика реализована вручную.
Основной способ внедрения повторных попыток — переопределение
tileLoadFunction. Эта функция вызывается для каждого тайла
при необходимости загрузки.
Ключевая идея: добавление счётчика попыток в объект изображения или через WeakMap.
import TileImage from 'ol/source/TileImage';
const maxRetries = 3;
const retryDelay = 500;
const source = new TileImage({
url: 'https://tile.server/{z}/{x}/{y}.png',
tileLoadFunction: function (tile, src) {
const img = tile.getImage();
img._retryCount = img._retryCount || 0;
const attemptLoad = () => {
img.src = src;
};
img.onl oad = () => {
img._retryCount = 0;
};
img.oner ror = () => {
if (img._retryCount < maxRetries) {
img._retryCount++;
setTimeout(attemptLoad, retryDelay * img._retryCount);
}
};
attemptLoad();
}
});
В данной схеме retry управляется полностью на уровне загрузчика изображения.
OpenLayers предоставляет события слоя и источника, позволяющие реагировать на ошибки загрузки тайлов:
tileloadstarttileloadendtileloaderrorСобытие tileloaderror позволяет централизованно
реализовать повторную загрузку без модификации
tileLoadFunction.
source.on('tileloaderror', function (event) {
const tile = event.tile;
const image = tile.getImage();
image._retryCount = image._retryCount || 0;
if (image._retryCount < 2) {
image._retryCount++;
setTimeout(() => {
tile.load();
}, 1000);
}
});
Метод tile.load() инициирует повторный запрос тайла
через стандартный pipeline источника.
При нестабильных сетях фиксированная задержка приводит к перегрузке сервера. Более устойчивый подход — экспоненциальная задержка:
function getDelay(attempt) {
return Math.min(1000 * 2 ** attempt, 8000);
}
Интеграция в обработчик ошибки:
img.oner ror = () => {
if (img._retryCount < maxRetries) {
const delay = getDelay(img._retryCount);
img._retryCount++;
setTimeout(() => {
img.src = src;
}, delay);
}
};
Источники изображений (ol/source/ImageStatic,
ol/source/ImageWMS) используют одиночный HTTP-запрос на
изображение. Ошибка загрузки приводит к полному отсутствию слоя.
Механизм retry реализуется через imageLoadFunction.
import ImageWMS from 'ol/source/ImageWMS';
const source = new ImageWMS({
url: 'https://server/wms',
params: { LAYERS: 'layer' },
imageLoadFunction: function (image, src) {
let attempts = 0;
const load = () => {
const img = image.getImage();
img.src = src;
};
const onEr ror = () => {
if (attempts < 3) {
attempts++;
setTimeout(load, 500 * attempts);
}
};
const img = image.getImage();
img.onl oad = () => {};
img.oner ror = onError;
load();
}
});
Особенность данного подхода — полная замена стандартного HTTP-процесса загрузки.
Векторные источники (ol/source/Vector) используют
loader функцию. Именно здесь чаще всего реализуется retry
для fetch-запросов.
import VectorSource from 'ol/source/Vector';
const source = new VectorSource({
loader: function (extent, resolution, projection) {
const url = 'https://server/data.geojson';
let attempt = 0;
const load = () => {
fetch(url)
.then(response => {
if (!response.ok) throw new Error('HTTP error');
return response.json();
})
.then(data => {
source.addFeatures(source.getFormat().readFeatures(data));
})
.catch(() => {
if (attempt < 3) {
attempt++;
setTimeout(load, 1000 * attempt);
}
});
};
load();
}
});
Здесь retry становится частью бизнес-логики загрузки данных, а не графического слоя.
При частых изменениях extent или zoom возможны гонки запросов. Retry без отмены предыдущих запросов приводит к избыточной нагрузке.
Использование AbortController позволяет управлять
устаревшими попытками:
let controller = null;
function loadWithRetry(url, attempt = 0) {
if (controller) controller.abort();
controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(data => {
source.addFeatures(format.readFeatures(data));
})
.catch(err => {
if (err.name === 'AbortError') return;
if (attempt < 3) {
setTimeout(() => loadWithRetry(url, attempt + 1), 500 * attempt);
}
});
}
Это предотвращает накопление устаревших retry-запросов при быстром взаимодействии с картой.
При масштабных приложениях retry не дублируется в каждом источнике, а выносится в универсальные функции:
function withRetry(fn, maxRetries = 3, delay = 500) {
return function (...args) {
let attempt = 0;
const exec = () => {
fn(...args).catch(() => {
if (attempt < maxRetries) {
attempt++;
setTimeout(exec, delay * attempt);
}
});
};
exec();
};
}
Применение к loader:
loader: withRetry((extent) => {
return fetch(url).then(r => r.json()).then(...);
})
Retry-логика должна учитывать различие типов ошибок:
В OpenLayers это важно, так как retry без фильтрации ошибок может привести к бесконечным циклам запросов при неверной конфигурации сервера.
Во многих случаях retry заменяется или дополняется кэшированием:
При наличии кэша повторные попытки становятся дешёвыми и не нагружают сервер.
В реальных приложениях применяется комбинация:
Такая комбинация критична для стабильной работы тайловых карт в условиях нестабильных сетей и распределённых серверов тайлов.