UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

249 lines (236 loc) 7.23 kB
import { EventDispatcher } from 'three'; import RequestQueue from '../core/RequestQueue'; import HttpConfiguration from './HttpConfiguration'; import PromiseUtils from './PromiseUtils'; import TextureGenerator from './TextureGenerator'; const DEFAULT_RETRY_DELAY_MS = 1000; const DEFAULT_CONCURRENT_REQUESTS_PER_HOST = 10; export class FetcherEventDispatcher extends EventDispatcher {} const eventTarget = new FetcherEventDispatcher(); /** * Adds a listener to an event type on fetch operations. * * @param type - The type of event to listen to - only `error` is supported. * @param listener - The function that gets called when the event is fired. */ function addEventListener(type, listener) { eventTarget.addEventListener(type, listener); } /** * Checks if listener is added to an event type. * * @param type - The type of event to listen to - only `error` is supported. * @param listener - The function that gets called when the event is fired. * @returns `true` if the listener is added to this event type. */ function hasEventListener(type, listener) { return eventTarget.hasEventListener(type, listener); } /** * Removes a listener from an event type on fetch operations. * * @param type - The type of the listener that gets removed. * @param listener - The listener function that gets removed. */ function removeEventListener(type, listener) { eventTarget.removeEventListener(type, listener); } const hostQueues = new Map(); let id = 0; function toNumericalPriority(priority) { if (priority == null || priority === 'auto') { return undefined; } if (priority === 'high') { return 1; } return -1; } /** * Queue an HTTP request. * * @param req - The request to queue. */ function enqueue(req, init) { const url = new URL(req.url); let queue = hostQueues.get(url.hostname); if (!queue) { queue = new RequestQueue({ maxConcurrentRequests: DEFAULT_CONCURRENT_REQUESTS_PER_HOST }); hostQueues.set(url.hostname, queue); } const doFetch = async () => { req.signal?.throwIfAborted(); const res = await fetch(req, init); return res; }; return queue.enqueue({ id: (id++).toString(), request: () => doFetch(), shouldExecute: req.signal == null ? undefined : () => !req.signal.aborted, priority: toNumericalPriority(init?.priority) }); } /** * @internal */ function getInfo() { let pending = 0; hostQueues.forEach(queue => { pending += queue.length; }); return { pending }; } /** * An error raised whenever the received response does not have a 2XX status. */ export class HttpError extends Error { constructor(response) { super(`${response.status} ${response.statusText} - ${response.url}`); this.response = response; } } /** * Wrapper over [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/fetch). * * Use this function instead of calling directly the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) * to benefit from automatic configuration from the {@link HttpConfiguration} module. * * fires `error` event On Network/HTTP error. * @param input - the fetch input * @param options - fetch options (passed directly to `fetch()`) * @returns The response object. */ async function fetchInternal(input, options) { const augmentedOptions = HttpConfiguration.applyConfiguration(input, options); const req = new Request(input, augmentedOptions); const response = await enqueue(req, { priority: options?.priority }).catch(error => { eventTarget.dispatchEvent({ type: 'error', error }); throw error; }); if (!response.ok) { const retries = options?.retries ?? 0; if (retries > 0) { const retryDelay = options?.retryDelay ?? DEFAULT_RETRY_DELAY_MS; if (retryDelay > 0) { await PromiseUtils.delay(retryDelay); } return fetchInternal(input, { ...options, retries: retries - 1 }); } else { const error = new HttpError(response); eventTarget.dispatchEvent({ type: 'error', error }); throw error; } } return response; } /** * Wrapper over `fetch`, then returns the blob of the response. * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - fetch options (passed directly to `fetch()`) * @returns The response blob. */ async function blob(input, options) { const response = await fetchInternal(input, options); return response.blob(); } /** * Wrapper over `fetch` to get some text * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - fetch options (passed directly to `fetch()`) * @returns the promise containing the text */ async function text(input, options) { const response = await fetchInternal(input, options); return response.text(); } /** * Wrapper over `fetch` to get some JSON * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - fetch options (passed directly to `fetch()`) * @returns the promise containing the JSON */ async function json(input, options) { const response = await fetchInternal(input, options); return response.json(); } /** * Wrapper over `fetch` to get some XML. * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - fetch options (passed directly to `fetch()`) * @returns the promise containing the XML */ async function xml(input, options) { const response = await fetchInternal(input, options); const txt = await response.text(); return new window.DOMParser().parseFromString(txt, 'text/xml'); } /** * Wrapper over `fetch` to get some `ArrayBuffer` * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - fetch options (passed directly to `fetch()`) * @returns the promise containing the ArrayBuffer */ async function arrayBuffer(input, options) { const response = await fetchInternal(input, options); return response.arrayBuffer(); } /** * Downloads a remote image and converts it into a texture. * * fires `error` event On Network/HTTP error. * @param input - the URL to fetch, or the resource request. * @param options - Texture creation options and fetch options (passed directly to `fetch()`) * @returns the promise containing the texture */ async function texture(input, options) { const data = await blob(input, options); return TextureGenerator.decodeBlob(data, options); } /** * Exposes an API to perform HTTP requests. * This should be used instead of the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) * in order to benefit from some error-checking, automatic configuration (from the * {@link HttpConfiguration} module), etc. * */ export default { fetch: fetchInternal, xml, json, blob, texture, arrayBuffer, text, /** @internal */ getInfo, addEventListener, hasEventListener, removeEventListener, /** @internal */ _eventTarget: eventTarget // Used for testing };