Большая часть современных приложений на базе CesiumJS работает с
удалёнными источниками данных. Трёхмерные тайлы, модели, изображения,
данные рельефа, GeoJSON-файлы и различные сетевые ресурсы загружаются
асинхронно. По этой причине обработка объектов Promise
занимает важное место в архитектуре приложений.
Promise представляет собой объект, содержащий результат
асинхронной операции. В момент создания результат ещё может быть
неизвестен, однако после завершения операции объект переходит в одно из
состояний:
Во многих API CesiumJS методы возвращают именно Promise,
поскольку загрузка данных происходит через сеть и требует времени.
Одним из наиболее распространённых примеров является загрузка набора трёхмерных тайлов.
const tileset = await Cesium.Cesium3DTileset.fromUrl(
"https://example.com/tileset.json"
);
viewer.scene.primitives.add(tileset);
Метод Cesium3DTileset.fromUrl() возвращает Promise.
Эквивалентный вариант через then():
Cesium.Cesium3DTileset.fromUrl(
"https://example.com/tileset.json"
)
.then(function(tileset) {
viewer.scene.primitives.add(tileset);
})
.catch(function(error) {
console.error(error);
});
После успешной загрузки объект tileset передаётся в
обработчик then().
Несколько асинхронных операций могут выполняться последовательно.
Cesium.Cesium3DTileset.fromUrl(
"https://example.com/tileset.json"
)
.then(function(tileset) {
viewer.scene.primitives.add(tileset);
return viewer.zoomTo(tileset);
})
.then(function() {
console.log("Камера перемещена");
})
.catch(function(error) {
console.error(error);
});
В данном примере:
Каждый вызов then() возвращает новый Promise, что
позволяет формировать длинные последовательности действий.
Сетевые операции всегда потенциально подвержены ошибкам:
Для обработки подобных ситуаций используется метод
catch().
Cesium.GeoJsonDataSource.load(
"data/countries.geojson"
)
.then(function(dataSource) {
viewer.dataSources.add(dataSource);
})
.catch(function(error) {
console.error("Ошибка загрузки:", error);
});
Если ошибка возникнет на любом этапе цепочки Promise, управление
будет передано в блок catch().
Современный JavaScript предоставляет синтаксис
async/await, который значительно упрощает работу с
Promise.
Без async/await:
Cesium.GeoJsonDataSource.load(url)
.then(function(dataSource) {
viewer.dataSources.add(dataSource);
return viewer.zoomTo(dataSource);
})
.catch(function(error) {
console.error(error);
});
С использованием async/await:
async function loadGeoJson() {
try {
const dataSource =
await Cesium.GeoJsonDataSource.load(url);
viewer.dataSources.add(dataSource);
await viewer.zoomTo(dataSource);
}
catch(error) {
console.error(error);
}
}
Такой код легче читать, особенно когда количество асинхронных операций увеличивается.
GeoJSON является одним из наиболее часто используемых форматов пространственных данных.
async function addCountries() {
try {
const source =
await Cesium.GeoJsonDataSource.load(
"countries.geojson"
);
viewer.dataSources.add(source);
}
catch(error) {
console.error(error);
}
}
Пока файл не будет полностью загружен и обработан, выполнение
остановится на операторе await.
Загрузка KML выполняется аналогичным образом.
async function loadKml() {
try {
const dataSource =
await Cesium.KmlDataSource.load(
"map.kml"
);
viewer.dataSources.add(dataSource);
}
catch(error) {
console.error(error);
}
}
Метод возвращает Promise, содержащий объект
KmlDataSource.
CZML используется для описания динамических объектов и временных интервалов.
async function loadCzml() {
try {
const source =
await Cesium.CzmlDataSource.load(
"aircraft.czml"
);
viewer.dataSources.add(source);
}
catch(error) {
console.error(error);
}
}
После завершения Promise появляется доступ к объекту источника данных.
Во многих проектах необходимо загружать сразу несколько ресурсов.
Например:
Для подобных задач используется Promise.all().
const promises = [
Cesium.GeoJsonDataSource.load("roads.geojson"),
Cesium.GeoJsonDataSource.load("cities.geojson"),
Cesium.GeoJsonDataSource.load("regions.geojson")
];
Promise.all(promises)
.then(function(results) {
results.forEach(function(dataSource) {
viewer.dataSources.add(dataSource);
});
})
.catch(function(error) {
console.error(error);
});
Все запросы запускаются одновременно.
Promise завершится успешно только тогда, когда будут успешно загружены все ресурсы.
Более современная реализация выглядит следующим образом:
async function loadLayers() {
try {
const [
roads,
cities,
regions
] = await Promise.all([
Cesium.GeoJsonDataSource.load(
"roads.geojson"
),
Cesium.GeoJsonDataSource.load(
"cities.geojson"
),
Cesium.GeoJsonDataSource.load(
"regions.geojson"
)
]);
viewer.dataSources.add(roads);
viewer.dataSources.add(cities);
viewer.dataSources.add(regions);
}
catch(error) {
console.error(error);
}
}
Такой подход обеспечивает высокую скорость загрузки, поскольку ресурсы не ожидают друг друга.
Иногда необходимо получить результаты всех операций независимо от ошибок отдельных запросов.
Для этого используется Promise.allSettled().
const results = await Promise.allSettled([
Cesium.GeoJsonDataSource.load("roads.geojson"),
Cesium.GeoJsonDataSource.load("cities.geojson"),
Cesium.GeoJsonDataSource.load("regions.geojson")
]);
Каждый элемент массива будет содержать:
{
status: "fulfilled",
value: ...
}
или
{
status: "rejected",
reason: ...
}
Это особенно полезно для приложений мониторинга и геопорталов, где отказ одного слоя не должен блокировать отображение остальных данных.
Метод Promise.race() возвращает результат первого
завершившегося Promise.
const result = await Promise.race([
requestA(),
requestB()
]);
На практике подобный механизм может использоваться для:
Метод Promise.any() завершится успешно, если хотя бы
один Promise выполнится без ошибки.
const source = await Promise.any([
loadFromServer1(),
loadFromServer2(),
loadFromServer3()
]);
Подход удобен при наличии нескольких независимых поставщиков пространственных данных.
Часто требуется предварительно получить данные через Fetch API, а затем передать их в CesiumJS.
async function loadData() {
try {
const response =
await fetch("/api/objects");
const json =
await response.json();
console.log(json);
}
catch(error) {
console.error(error);
}
}
Каждый вызов await фактически ожидает завершения
соответствующего Promise.
Fetch не генерирует исключение при ошибках HTTP.
Поэтому рекомендуется выполнять дополнительную проверку.
async function loadData() {
const response =
await fetch("/api/data");
if (!response.ok) {
throw new Error(
"HTTP Error " + response.status
);
}
return response.json();
}
После генерации исключения управление перейдёт в блок
catch.
Иногда необходимо обернуть старый асинхронный код в Promise.
function wait(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
Использование:
await wait(3000);
console.log("Прошло 3 секунды");
Подобный механизм может применяться при управлении анимациями и сценариями отображения данных.
Иногда следующий ресурс зависит от предыдущего.
async function loadScenario() {
const buildings =
await Cesium.GeoJsonDataSource.load(
"buildings.geojson"
);
viewer.dataSources.add(buildings);
const routes =
await Cesium.GeoJsonDataSource.load(
"routes.geojson"
);
viewer.dataSources.add(routes);
}
В этом случае загрузка выполняется строго по порядку.
Последовательная схема:
await loadA();
await loadB();
await loadC();
Параллельная схема:
await Promise.all([
loadA(),
loadB(),
loadC()
]);
Параллельный вариант обычно работает значительно быстрее, поскольку запросы отправляются одновременно.
Крупные приложения нередко выполняют полную подготовку сцены до начала работы.
async function initialize() {
const terrain =
await Cesium.CesiumTerrainProvider.fromIonAssetId(
1
);
const viewer = new Cesium.Viewer(
"cesiumContainer",
{
terrainProvider: terrain
}
);
const buildings =
await Cesium.Cesium3DTileset.fromUrl(
"tileset.json"
);
viewer.scene.primitives.add(buildings);
await viewer.zoomTo(buildings);
return viewer;
}
Каждый этап завершается до начала следующего, что обеспечивает предсказуемость процесса инициализации.
const dataSource =
Cesium.GeoJsonDataSource.load(url);
viewer.dataSources.add(dataSource);
Ошибка заключается в том, что dataSource содержит
Promise, а не объект GeoJsonDataSource.
Правильный вариант:
const dataSource =
await Cesium.GeoJsonDataSource.load(url);
viewer.dataSources.add(dataSource);
Cesium.GeoJsonDataSource.load(url);
При возникновении ошибки информация о ней может быть потеряна.
Корректный вариант:
Cesium.GeoJsonDataSource.load(url)
.catch(console.error);
Нежелательно одновременно использовать длинные цепочки
then() и await.
Плохо:
await loadData()
.then(processData)
.then(renderData);
Лучше придерживаться одного подхода:
const data =
await loadData();
const processed =
processData(data);
renderData(processed);
Использовать async/await как основной стиль
разработки.
Оборачивать асинхронные операции в
try/catch.
Выполнять независимые запросы через
Promise.all().
Проверять сетевые ошибки и HTTP-статусы.
Не блокировать загрузкой одного ресурса загрузку остальных без необходимости.
Разделять этапы получения данных, обработки данных и отображения данных.
Логировать ошибки загрузки тайлов, моделей и пространственных слоёв.
Грамотное использование Promise позволяет строить масштабируемые приложения CesiumJS, эффективно работающие с большими объёмами геопространственных данных, сетевыми сервисами и сложными сценариями визуализации в реальном времени.