module import "@here/harp-fetch" ; import { DeferredPromise } from "./DeferredPromise" ; /**
 * Abstract interface for a transfer manager.
 *
 * Provides functionality for downloading JSON or ArrayBuffers.
 * Implementations typically implement retry on server congestion,
 * limit the maximum amount of parallel downloads or merge duplicate
 * downloads.
 */ export interface ITransferManager { param url param init downloadJson < T > ( url , init ? ) : Promise < T > ; param url param init downloadArrayBuffer ( url , init ? ) : Promise < ArrayBuffer > ; param url param init download ( url , init ? ) : Promise < Response > ; } /**
 * `TransferManager` for downloading URLs.
 *
 * Features:
 *
 * * Merges JSON downloads, for example, the same URL if requested as JSON will only be downloaded
 *   once.
 * * Limits the amount of parallel downloads, useful when requesting a large amount of URLs that
 *   would otherwise stall the browser.
 * * Retries the downloads with an increasing timeout on HTTP 503 replies.
 *
 * The static method [[instance]] can be used to get a default constructed instance.
 */ export class TransferManager implements ITransferManager { /**
     * The timeout in milliseconds to wait between retries. This timeout is multiplied with the
     * number of retries. First retry waits for 0 ms, second retry for 500 ms, third for 1000 ms and
     * so on.
     */ static readonly retryTimeout = 500 ; /**
     * The amount of maximum parallel downloads to allow.
     */ static readonly maxParallelDownloads = 16 ; /**
     * Returns a default instance of [[TransferManager]].
     */ static instance ( ) { return TransferManager . defaultInstance ; } private static readonly defaultInstance = new TransferManager ( ) ; private static async fetchRepeatedly ( fetchFunction , retryCount , maxRetries , url , init ? ) { try { if ( retryCount < maxRetries ) { const response = await fetchFunction ( url , init ) ; if ( response . status === 200 || response . status === 204 ) { return response ; } else if ( response . status >= 400 && response . status < 500 ) { retryCount = maxRetries ; const responseText = await response . text ( ) ; throw new Error ( responseText ) ; } } else { throw new Error ( "Max number of retries reached" ) ; } } catch ( err ) { if ( err . hasOwnProperty ( "isCancelled" ) || err . name === "AbortError" || retryCount >= maxRetries ) { throw err ; } } return await TransferManager . waitFor ( TransferManager . retryTimeout * retryCount ) . then ( ( ) => TransferManager . fetchRepeatedly ( fetchFunction , retryCount + 1 , maxRetries , url , init ) ) ; } private static waitFor ( milliseconds ) { return new Promise < void > ( resolve => setTimeout ( resolve , milliseconds ) ) ; } private activeDownloadCount = 0 ; private readonly downloadQueue = new Array < DeferredPromise < Response > > ( ) ; private readonly activeDownloads = new Map < RequestInfo , Promise < any > > ( ) ; param fetchFunction param maxRetries constructor ( readonly fetchFunction = fetch , readonly maxRetries = 5 ) { } param url param init downloadJson < T > ( url , init ? ) { return this . downloadAs < T > ( response => response . json ( ) , url , init ) ; } param url param init downloadArrayBuffer ( url , init ? ) { return this . download ( url , init ) . then ( response => response . arrayBuffer ( ) ) ; } param url param init download ( url , init ? ) { if ( this . activeDownloadCount >= TransferManager . maxParallelDownloads ) { const deferred = new DeferredPromise < Response > ( ( ) => this . doDownload ( url , init ) ) ; this . downloadQueue . push ( deferred ) ; return deferred . promise ; } return this . doDownload ( url , init ) ; } private async doDownload ( url , init ? ) { try { ++ this . activeDownloadCount ; const response = await TransferManager . fetchRepeatedly ( this . fetchFunction , 0 , this . maxRetries , url , init ) ; this . onDownloadDone ( ) ; return response ; } catch ( error ) { this . onDownloadDone ( ) ; throw error ; } } private onDownloadDone ( ) { -- this . activeDownloadCount ; this . execDeferredDownload ( ) ; } private execDeferredDownload ( ) { const future = this . downloadQueue . pop ( ) ; if ( future === undefined ) { return ; } future . exec ( ) ; } private downloadAs < T > ( converter , url , init ? ) { const cacheKey = url ; const pendingFetch = this . activeDownloads . get ( cacheKey ) ; if ( pendingFetch !== undefined ) { return Promise . resolve ( pendingFetch ) ; } const newFetch = this . download ( url , init ) . then ( response => { this . activeDownloads . delete ( cacheKey ) ; if ( response . ok ) { return converter ( response ) ; } throw new Error ( JSON . stringify ( response ) ) ; } ) . catch ( err => { this . activeDownloads . delete ( cacheKey ) ; throw err ; } ) ; this . activeDownloads . set ( cacheKey , newFetch ) ; return newFetch ; } }