Библиотека Turf.js предоставляет набор чистых синхронных функций для геообработки: вычисление расстояний, буферов, пересечений, объединений и других операций над GeoJSON. Внутри самой библиотеки отсутствует асинхронность, поскольку алгоритмы являются детерминированными и работают с уже загруженными структурами данных. Асинхронность возникает на уровне интеграции: получение данных, обработка больших наборов, распределение вычислений и взаимодействие с внешними API.
Асинхронный код в контексте Turf.js появляется не из-за самой библиотеки, а из-за окружения:
Каждый из этих сценариев требует управления конкурентностью и жизненным циклом данных до передачи их в Turf-функции.
Типичный сценарий начинается с загрузки GeoJSON:
async function loadGeoJSON(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
}
После получения данных они становятся входом для синхронных операций Turf.js:
import { buffer } fr om "@turf/turf";
async function process() {
const data = await loadGeoJSON("/data/roads.geojson");
const result = buffer(data, 0.5, { units: "kilometers" });
return result;
}
Асинхронность здесь завершает свою роль до момента геообработки: Turf.js работает уже с готовым объектом.
Ключевой принцип при работе с Turf.js — отделение ввода-вывода от вычислений. I/O операции всегда асинхронны, а геометрические операции — синхронны и CPU-bound.
Пример комбинированного пайплайна:
async function pipeline() {
const [roads, rivers] = await Promise.all([
fetch("/roads.geojson").then(r => r.json()),
fetch("/rivers.geojson").then(r => r.json())
]);
const bufferedRivers = buffer(rivers, 1, { units: "kilometers" });
return bufferedRivers;
}
Использование Promise.all уменьшает время ожидания
загрузки и позволяет параллелизовать I/O.
При работе с крупными GeoJSON-файлами синхронные операции Turf.js могут блокировать основной поток. Асинхронность в этом случае используется для разбиения работы на части.
async function processInBatches(features, batchSize) {
const results = [];
for (let i = 0; i < features.length; i += batchSize) {
const batch = features.slice(i, i + batchSize);
const processed = batch.map(feature =>
buffer(feature, 0.2, { units: "kilometers" })
);
results.push(...processed);
await new Promise(resolve => setTimeout(resolve, 0));
}
return results;
}
Задержка через setTimeout позволяет отдавать управление
event loop и предотвращает “заморозку” интерфейса в браузере.
Для тяжёлых геоопераций применяется вынос Turf.js в отдельный поток. Это особенно важно при:
// worker.js
importScripts("https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js");
self.onmess age = function (e) {
const { feature, distance } = e.data;
const result = turf.buffer(feature, distance, {
units: "kilometers"
});
self.postMessage(result);
};
function runWorker(feature) {
return new Promise((resolve) => {
const worker = new Worker("worker.js");
worker.onmess age = (e) => {
resolve(e.data);
worker.terminate();
};
worker.postMessage({
feature,
distance: 1
});
});
}
Асинхронность здесь реализуется через Promise,
оборачивающий коммуникацию с worker.
При сложной геообработке формируется цепочка этапов:
async function geoPipeline(url) {
const data = await fetch(url).then(r => r.json());
const filtered = data.features.filter(f => f.geometry !== null);
const processed = filtered.map(f =>
buffer(f, 0.3, { units: "kilometers" })
);
return {
type: "FeatureCollection",
features: processed
};
}
Каждый этап может быть изолирован и потенциально вынесен в отдельный асинхронный шаг.
Хотя Turf.js не предоставляет встроенной параллельной обработки, асинхронность позволяет организовать конкурентное выполнение через:
Promise.allasync function parallelBuffer(features) {
const tasks = features.map(feature =>
Promise.resolve(buffer(feature, 0.5, { units: "kilometers" }))
);
return await Promise.all(tasks);
}
Такой подход эффективен только при умеренных объёмах данных, так как синхронная CPU-нагрузка всё равно выполняется в основном потоке.
При большом количестве объектов требуется ограничение числа одновременно выполняемых задач:
async function mapWithLimit(items, lim it, fn) {
const results = [];
let index = 0;
const workers = Array.from({ length: limit }, async () => {
while (index < items.length) {
const current = index++;
results[current] = await fn(items[current]);
}
});
await Promise.all(workers);
return results;
}
Использование этого подхода позволяет равномерно распределить нагрузку при комбинировании Turf.js с асинхронными операциями.
В сценариях GIS-платформ данные могут поступать потоками: например, через WebSocket или серверные события.
async function handleStream(stream) {
for await (const chunk of stream) {
const geo = JSON.parse(chunk);
const enriched = buffer(geo, 0.1, {
units: "kilometers"
});
console.log(enriched);
}
}
Асинхронный итератор позволяет обрабатывать данные по мере поступления, не дожидаясь завершения потока.
Часто Turf.js используется вместе с внешними сервисами:
async function enrichFeature(feature) {
const coords = feature.geometry.coordinates;
const response = await fetch(
`https://api.example.com/geocode?lon=${coords[0]}&lat=${coords[1]}`
);
const meta = await response.json();
return {
...feature,
properties: {
...feature.properties,
...meta
}
};
}
После обогащения данных выполняются Turf-операции:
const enriched = await enrichFeature(feature);
const result = buffer(enriched, 0.5, { units: "kilometers" });
Оборачивание map в Promise.all не делает
вычисления параллельными — они всё равно выполняются в главном
потоке.
await Promise.resolve(buffer(feature, 1));
Такой код не даёт преимуществ и только усложняет логику.
Большие коллекции GeoJSON без батчинга могут приводить к зависанию интерфейса даже при использовании async/await.
Чёткое разделение стадий обработки с возможностью масштабирования отдельных этапов.
Фиксированное количество воркеров, распределяющих Turf-операции.
Асинхронность в экосистеме Turf.js является внешним слоем управления данными и вычислениями. Библиотека остаётся чистым синхронным ядром геоалгоритмов, а асинхронные конструкции JavaScript используются для: