Skip to content

⬅️ Back to Table of Contents

📄 FileLoader.js

📊 Analysis Summary

Metric Count
🔧 Functions 5
🧱 Classes 2
📦 Imports 2
📊 Variables & Constants 20
⚡ Async/Await Patterns 1

📚 Table of Contents

🛠️ File Location:

📂 src/loaders/FileLoader.js

📦 Imports

Name Source
Cache ./Cache.js
Loader ./Loader.js

Variables & Constants

Name Type Kind Value Exported
loading {} let/var {}
req Request let/var new Request( url, { headers: new Headers( this.requestHeader ), credentials: ...
mimeType string let/var this.mimeType
responseType "" \| "arraybuffer" \| "blob" \| "doc... let/var this.responseType
callbacks any let/var loading[ url ]
contentLength string let/var response.headers.get( 'X-File-Size' ) \|\| response.headers.get( 'Content-Len...
total number let/var contentLength ? parseInt( contentLength ) : 0
lengthComputable boolean let/var total !== 0
loaded number let/var 0
event ProgressEvent<EventTarget> let/var new ProgressEvent( 'progress', { lengthComputable, loaded, total } )
callback any let/var callbacks[ i ]
stream ReadableStream<any> let/var new ReadableStream( { start( controller ) { readData(); function readData() {...
parser DOMParser let/var new DOMParser()
re RegExp let/var /charset="?([^;"\s]*)"?/i
label string let/var exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined
decoder TextDecoder let/var new TextDecoder( label )
callbacks any let/var loading[ url ]
callback any let/var callbacks[ i ]
callbacks any let/var loading[ url ]
callback any let/var callbacks[ i ]

Async/Await Patterns

Type Function Await Expressions Promise Chains
promise-chain readData none reader.read().then

Functions

FileLoader.load(url: string, onLoad: (arg0: any) => any, onProgress: onProgressCallback, onError: onErrorCallback): any

JSDoc:

/**
     * Starts loading from the given URL and pass the loaded response to the `onLoad()` callback.
     *
     * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
     * @param {function(any)} onLoad - Executed when the loading process has been finished.
     * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress.
     * @param {onErrorCallback} [onError] - Executed when errors occur.
     * @return {any|undefined} The cached resource if available.
     */

Parameters:

  • url string
  • onLoad (arg0: any) => any
  • onProgress onProgressCallback
  • onError onErrorCallback

Returns: any

Calls:

  • this.manager.resolveURL
  • Cache.get
  • this.manager.itemStart
  • setTimeout
  • onLoad
  • this.manager.itemEnd
  • loading[ url ].push
  • AbortSignal.any
  • `fetch( req ) .then( response => {
            if ( response.status === 200 || response.status === 0 ) {
    
                // Some browsers return HTTP Status 0 when using non-http protocol
                // e.g. 'file://' or 'data://'. Handle as success.
    
                if ( response.status === 0 ) {
    
                    console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
    
                }
    
                // Workaround: Checking if response.body === undefined for Alipay browser #23548
    
                if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) {
    
                    return response;
    
                }
    
                const callbacks = loading[ url ];
                const reader = response.body.getReader();
    
                // Nginx needs X-File-Size check
                // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
                const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' );
                const total = contentLength ? parseInt( contentLength ) : 0;
                const lengthComputable = total !== 0;
                let loaded = 0;
    
                // periodically read data into the new stream tracking while download progress
                const stream = new ReadableStream( {
                    start( controller ) {
    
                        readData();
    
                        function readData() {
    
                            reader.read().then( ( { done, value } ) => {
    
                                if ( done ) {
    
                                    controller.close();
    
                                } else {
    
                                    loaded += value.byteLength;
    
                                    const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
                                    for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
    
                                        const callback = callbacks[ i ];
                                        if ( callback.onProgress ) callback.onProgress( event );
    
                                    }
    
                                    controller.enqueue( value );
                                    readData();
    
                                }
    
                            }, ( e ) => {
    
                                controller.error( e );
    
                            } );
    
                        }
    
                    }
    
                } );
    
                return new Response( stream );
    
            } else {
    
                throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response );
    
            }
    
        } )
        .then( response => {
    
            switch ( responseType ) {
    
                case 'arraybuffer':
    
                    return response.arrayBuffer();
    
                case 'blob':
    
                    return response.blob();
    
                case 'document':
    
                    return response.text()
                        .then( text => {
    
                            const parser = new DOMParser();
                            return parser.parseFromString( text, mimeType );
    
                        } );
    
                case 'json':
    
                    return response.json();
    
                default:
    
                    if ( mimeType === '' ) {
    
                        return response.text();
    
                    } else {
    
                        // sniff encoding
                        const re = /charset="?([^;"\s]*)"?/i;
                        const exec = re.exec( mimeType );
                        const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined;
                        const decoder = new TextDecoder( label );
                        return response.arrayBuffer().then( ab => decoder.decode( ab ) );
    
                    }
    
            }
    
        } )
        .then( data => {
    
            // Add to cache only on HTTP success, so that we do not cache
            // error response bodies as proper responses to requests.
            Cache.add( `file:${url}`, data );
    
            const callbacks = loading[ url ];
            delete loading[ url ];
    
            for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
    
                const callback = callbacks[ i ];
                if ( callback.onLoad ) callback.onLoad( data );
    
            }
    
        } )
        .catch( err => {
    
            // Abort errors and other errors are handled the same
    
            const callbacks = loading[ url ];
    
            if ( callbacks === undefined ) {
    
                // When onLoad was called and url was deleted in `loading`
                this.manager.itemError( url );
                throw err;
    
            }
    
            delete loading[ url ];
    
            for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
    
                const callback = callbacks[ i ];
                if ( callback.onError ) callback.onError( err );
    
            }
    
            this.manager.itemError( url );
    
        } )
        .finally`
    

Internal Comments:

// Check if request is duplicate
// Initialise array for duplicate requests (x4)
// create request (x2)
// record states ( avoid data race ) (x2)
// start the fetch (x13)
// Some browsers return HTTP Status 0 when using non-http protocol
// e.g. 'file://' or 'data://'. Handle as success.
// Workaround: Checking if response.body === undefined for Alipay browser #23548
// Nginx needs X-File-Size check (x2)
// https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content (x2)
// periodically read data into the new stream tracking while download progress (x2)
// sniff encoding (x2)
// Add to cache only on HTTP success, so that we do not cache (x4)
// error response bodies as proper responses to requests. (x4)
// Abort errors and other errors are handled the same (x2)
// When onLoad was called and url was deleted in `loading` (x5)

Code
load( url, onLoad, onProgress, onError ) {

        if ( url === undefined ) url = '';

        if ( this.path !== undefined ) url = this.path + url;

        url = this.manager.resolveURL( url );

        const cached = Cache.get( `file:${url}` );

        if ( cached !== undefined ) {

            this.manager.itemStart( url );

            setTimeout( () => {

                if ( onLoad ) onLoad( cached );

                this.manager.itemEnd( url );

            }, 0 );

            return cached;

        }

        // Check if request is duplicate

        if ( loading[ url ] !== undefined ) {

            loading[ url ].push( {

                onLoad: onLoad,
                onProgress: onProgress,
                onError: onError

            } );

            return;

        }

        // Initialise array for duplicate requests
        loading[ url ] = [];

        loading[ url ].push( {
            onLoad: onLoad,
            onProgress: onProgress,
            onError: onError,
        } );

        // create request
        const req = new Request( url, {
            headers: new Headers( this.requestHeader ),
            credentials: this.withCredentials ? 'include' : 'same-origin',
            signal: ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal
        } );

        // record states ( avoid data race )
        const mimeType = this.mimeType;
        const responseType = this.responseType;

        // start the fetch
        fetch( req )
            .then( response => {

                if ( response.status === 200 || response.status === 0 ) {

                    // Some browsers return HTTP Status 0 when using non-http protocol
                    // e.g. 'file://' or 'data://'. Handle as success.

                    if ( response.status === 0 ) {

                        console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );

                    }

                    // Workaround: Checking if response.body === undefined for Alipay browser #23548

                    if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) {

                        return response;

                    }

                    const callbacks = loading[ url ];
                    const reader = response.body.getReader();

                    // Nginx needs X-File-Size check
                    // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
                    const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' );
                    const total = contentLength ? parseInt( contentLength ) : 0;
                    const lengthComputable = total !== 0;
                    let loaded = 0;

                    // periodically read data into the new stream tracking while download progress
                    const stream = new ReadableStream( {
                        start( controller ) {

                            readData();

                            function readData() {

                                reader.read().then( ( { done, value } ) => {

                                    if ( done ) {

                                        controller.close();

                                    } else {

                                        loaded += value.byteLength;

                                        const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
                                        for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                                            const callback = callbacks[ i ];
                                            if ( callback.onProgress ) callback.onProgress( event );

                                        }

                                        controller.enqueue( value );
                                        readData();

                                    }

                                }, ( e ) => {

                                    controller.error( e );

                                } );

                            }

                        }

                    } );

                    return new Response( stream );

                } else {

                    throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response );

                }

            } )
            .then( response => {

                switch ( responseType ) {

                    case 'arraybuffer':

                        return response.arrayBuffer();

                    case 'blob':

                        return response.blob();

                    case 'document':

                        return response.text()
                            .then( text => {

                                const parser = new DOMParser();
                                return parser.parseFromString( text, mimeType );

                            } );

                    case 'json':

                        return response.json();

                    default:

                        if ( mimeType === '' ) {

                            return response.text();

                        } else {

                            // sniff encoding
                            const re = /charset="?([^;"\s]*)"?/i;
                            const exec = re.exec( mimeType );
                            const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined;
                            const decoder = new TextDecoder( label );
                            return response.arrayBuffer().then( ab => decoder.decode( ab ) );

                        }

                }

            } )
            .then( data => {

                // Add to cache only on HTTP success, so that we do not cache
                // error response bodies as proper responses to requests.
                Cache.add( `file:${url}`, data );

                const callbacks = loading[ url ];
                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onLoad ) callback.onLoad( data );

                }

            } )
            .catch( err => {

                // Abort errors and other errors are handled the same

                const callbacks = loading[ url ];

                if ( callbacks === undefined ) {

                    // When onLoad was called and url was deleted in `loading`
                    this.manager.itemError( url );
                    throw err;

                }

                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onError ) callback.onError( err );

                }

                this.manager.itemError( url );

            } )
            .finally( () => {

                this.manager.itemEnd( url );

            } );

        this.manager.itemStart( url );

    }

FileLoader.setResponseType(value: "" | "arraybuffer" | "blob" | "document" | "json"): FileLoader

JSDoc:

/**
     * Sets the expected response type.
     *
     * @param {('arraybuffer'|'blob'|'document'|'json'|'')} value - The response type.
     * @return {FileLoader} A reference to this file loader.
     */

Parameters:

  • value "" | "arraybuffer" | "blob" | "document" | "json"

Returns: FileLoader

Code
setResponseType( value ) {

        this.responseType = value;
        return this;

    }

FileLoader.setMimeType(value: string): FileLoader

JSDoc:

/**
     * Sets the expected mime type of the loaded file.
     *
     * @param {string} value - The mime type.
     * @return {FileLoader} A reference to this file loader.
     */

Parameters:

  • value string

Returns: FileLoader

Code
setMimeType( value ) {

        this.mimeType = value;
        return this;

    }

FileLoader.abort(): FileLoader

JSDoc:

/**
     * Aborts ongoing fetch requests.
     *
     * @return {FileLoader} A reference to this instance.
     */

Returns: FileLoader

Calls:

  • this._abortController.abort
Code
abort() {

        this._abortController.abort();
        this._abortController = new AbortController();

        return this;

    }

readData(): void

Returns: void

Calls:

  • reader.read().then
  • controller.close
  • callback.onProgress
  • controller.enqueue
  • readData
  • controller.error
Code
function readData() {

                                reader.read().then( ( { done, value } ) => {

                                    if ( done ) {

                                        controller.close();

                                    } else {

                                        loaded += value.byteLength;

                                        const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
                                        for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                                            const callback = callbacks[ i ];
                                            if ( callback.onProgress ) callback.onProgress( event );

                                        }

                                        controller.enqueue( value );
                                        readData();

                                    }

                                }, ( e ) => {

                                    controller.error( e );

                                } );

                            }

Classes

HttpError

Class Code
class HttpError extends Error {

    constructor( message, response ) {

        super( message );
        this.response = response;

    }

}

FileLoader

Class Code
class FileLoader extends Loader {

    /**
     * Constructs a new file loader.
     *
     * @param {LoadingManager} [manager] - The loading manager.
     */
    constructor( manager ) {

        super( manager );

        /**
         * The expected mime type. Valid values can be found
         * [here]{@link hhttps://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromString#mimetype}
         *
         * @type {string}
         */
        this.mimeType = '';

        /**
         * The expected response type.
         *
         * @type {('arraybuffer'|'blob'|'document'|'json'|'')}
         * @default ''
         */
        this.responseType = '';

        /**
         * Used for aborting requests.
         *
         * @private
         * @type {AbortController}
         */
        this._abortController = new AbortController();

    }

    /**
     * Starts loading from the given URL and pass the loaded response to the `onLoad()` callback.
     *
     * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
     * @param {function(any)} onLoad - Executed when the loading process has been finished.
     * @param {onProgressCallback} [onProgress] - Executed while the loading is in progress.
     * @param {onErrorCallback} [onError] - Executed when errors occur.
     * @return {any|undefined} The cached resource if available.
     */
    load( url, onLoad, onProgress, onError ) {

        if ( url === undefined ) url = '';

        if ( this.path !== undefined ) url = this.path + url;

        url = this.manager.resolveURL( url );

        const cached = Cache.get( `file:${url}` );

        if ( cached !== undefined ) {

            this.manager.itemStart( url );

            setTimeout( () => {

                if ( onLoad ) onLoad( cached );

                this.manager.itemEnd( url );

            }, 0 );

            return cached;

        }

        // Check if request is duplicate

        if ( loading[ url ] !== undefined ) {

            loading[ url ].push( {

                onLoad: onLoad,
                onProgress: onProgress,
                onError: onError

            } );

            return;

        }

        // Initialise array for duplicate requests
        loading[ url ] = [];

        loading[ url ].push( {
            onLoad: onLoad,
            onProgress: onProgress,
            onError: onError,
        } );

        // create request
        const req = new Request( url, {
            headers: new Headers( this.requestHeader ),
            credentials: this.withCredentials ? 'include' : 'same-origin',
            signal: ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal
        } );

        // record states ( avoid data race )
        const mimeType = this.mimeType;
        const responseType = this.responseType;

        // start the fetch
        fetch( req )
            .then( response => {

                if ( response.status === 200 || response.status === 0 ) {

                    // Some browsers return HTTP Status 0 when using non-http protocol
                    // e.g. 'file://' or 'data://'. Handle as success.

                    if ( response.status === 0 ) {

                        console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );

                    }

                    // Workaround: Checking if response.body === undefined for Alipay browser #23548

                    if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) {

                        return response;

                    }

                    const callbacks = loading[ url ];
                    const reader = response.body.getReader();

                    // Nginx needs X-File-Size check
                    // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
                    const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' );
                    const total = contentLength ? parseInt( contentLength ) : 0;
                    const lengthComputable = total !== 0;
                    let loaded = 0;

                    // periodically read data into the new stream tracking while download progress
                    const stream = new ReadableStream( {
                        start( controller ) {

                            readData();

                            function readData() {

                                reader.read().then( ( { done, value } ) => {

                                    if ( done ) {

                                        controller.close();

                                    } else {

                                        loaded += value.byteLength;

                                        const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
                                        for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                                            const callback = callbacks[ i ];
                                            if ( callback.onProgress ) callback.onProgress( event );

                                        }

                                        controller.enqueue( value );
                                        readData();

                                    }

                                }, ( e ) => {

                                    controller.error( e );

                                } );

                            }

                        }

                    } );

                    return new Response( stream );

                } else {

                    throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response );

                }

            } )
            .then( response => {

                switch ( responseType ) {

                    case 'arraybuffer':

                        return response.arrayBuffer();

                    case 'blob':

                        return response.blob();

                    case 'document':

                        return response.text()
                            .then( text => {

                                const parser = new DOMParser();
                                return parser.parseFromString( text, mimeType );

                            } );

                    case 'json':

                        return response.json();

                    default:

                        if ( mimeType === '' ) {

                            return response.text();

                        } else {

                            // sniff encoding
                            const re = /charset="?([^;"\s]*)"?/i;
                            const exec = re.exec( mimeType );
                            const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined;
                            const decoder = new TextDecoder( label );
                            return response.arrayBuffer().then( ab => decoder.decode( ab ) );

                        }

                }

            } )
            .then( data => {

                // Add to cache only on HTTP success, so that we do not cache
                // error response bodies as proper responses to requests.
                Cache.add( `file:${url}`, data );

                const callbacks = loading[ url ];
                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onLoad ) callback.onLoad( data );

                }

            } )
            .catch( err => {

                // Abort errors and other errors are handled the same

                const callbacks = loading[ url ];

                if ( callbacks === undefined ) {

                    // When onLoad was called and url was deleted in `loading`
                    this.manager.itemError( url );
                    throw err;

                }

                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onError ) callback.onError( err );

                }

                this.manager.itemError( url );

            } )
            .finally( () => {

                this.manager.itemEnd( url );

            } );

        this.manager.itemStart( url );

    }

    /**
     * Sets the expected response type.
     *
     * @param {('arraybuffer'|'blob'|'document'|'json'|'')} value - The response type.
     * @return {FileLoader} A reference to this file loader.
     */
    setResponseType( value ) {

        this.responseType = value;
        return this;

    }

    /**
     * Sets the expected mime type of the loaded file.
     *
     * @param {string} value - The mime type.
     * @return {FileLoader} A reference to this file loader.
     */
    setMimeType( value ) {

        this.mimeType = value;
        return this;

    }

    /**
     * Aborts ongoing fetch requests.
     *
     * @return {FileLoader} A reference to this instance.
     */
    abort() {

        this._abortController.abort();
        this._abortController = new AbortController();

        return this;

    }

}

Methods

load(url: string, onLoad: (arg0: any) => any, onProgress: onProgressCallback, onError: onErrorCallback): any
Code
load( url, onLoad, onProgress, onError ) {

        if ( url === undefined ) url = '';

        if ( this.path !== undefined ) url = this.path + url;

        url = this.manager.resolveURL( url );

        const cached = Cache.get( `file:${url}` );

        if ( cached !== undefined ) {

            this.manager.itemStart( url );

            setTimeout( () => {

                if ( onLoad ) onLoad( cached );

                this.manager.itemEnd( url );

            }, 0 );

            return cached;

        }

        // Check if request is duplicate

        if ( loading[ url ] !== undefined ) {

            loading[ url ].push( {

                onLoad: onLoad,
                onProgress: onProgress,
                onError: onError

            } );

            return;

        }

        // Initialise array for duplicate requests
        loading[ url ] = [];

        loading[ url ].push( {
            onLoad: onLoad,
            onProgress: onProgress,
            onError: onError,
        } );

        // create request
        const req = new Request( url, {
            headers: new Headers( this.requestHeader ),
            credentials: this.withCredentials ? 'include' : 'same-origin',
            signal: ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal
        } );

        // record states ( avoid data race )
        const mimeType = this.mimeType;
        const responseType = this.responseType;

        // start the fetch
        fetch( req )
            .then( response => {

                if ( response.status === 200 || response.status === 0 ) {

                    // Some browsers return HTTP Status 0 when using non-http protocol
                    // e.g. 'file://' or 'data://'. Handle as success.

                    if ( response.status === 0 ) {

                        console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );

                    }

                    // Workaround: Checking if response.body === undefined for Alipay browser #23548

                    if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) {

                        return response;

                    }

                    const callbacks = loading[ url ];
                    const reader = response.body.getReader();

                    // Nginx needs X-File-Size check
                    // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
                    const contentLength = response.headers.get( 'X-File-Size' ) || response.headers.get( 'Content-Length' );
                    const total = contentLength ? parseInt( contentLength ) : 0;
                    const lengthComputable = total !== 0;
                    let loaded = 0;

                    // periodically read data into the new stream tracking while download progress
                    const stream = new ReadableStream( {
                        start( controller ) {

                            readData();

                            function readData() {

                                reader.read().then( ( { done, value } ) => {

                                    if ( done ) {

                                        controller.close();

                                    } else {

                                        loaded += value.byteLength;

                                        const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
                                        for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                                            const callback = callbacks[ i ];
                                            if ( callback.onProgress ) callback.onProgress( event );

                                        }

                                        controller.enqueue( value );
                                        readData();

                                    }

                                }, ( e ) => {

                                    controller.error( e );

                                } );

                            }

                        }

                    } );

                    return new Response( stream );

                } else {

                    throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response );

                }

            } )
            .then( response => {

                switch ( responseType ) {

                    case 'arraybuffer':

                        return response.arrayBuffer();

                    case 'blob':

                        return response.blob();

                    case 'document':

                        return response.text()
                            .then( text => {

                                const parser = new DOMParser();
                                return parser.parseFromString( text, mimeType );

                            } );

                    case 'json':

                        return response.json();

                    default:

                        if ( mimeType === '' ) {

                            return response.text();

                        } else {

                            // sniff encoding
                            const re = /charset="?([^;"\s]*)"?/i;
                            const exec = re.exec( mimeType );
                            const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined;
                            const decoder = new TextDecoder( label );
                            return response.arrayBuffer().then( ab => decoder.decode( ab ) );

                        }

                }

            } )
            .then( data => {

                // Add to cache only on HTTP success, so that we do not cache
                // error response bodies as proper responses to requests.
                Cache.add( `file:${url}`, data );

                const callbacks = loading[ url ];
                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onLoad ) callback.onLoad( data );

                }

            } )
            .catch( err => {

                // Abort errors and other errors are handled the same

                const callbacks = loading[ url ];

                if ( callbacks === undefined ) {

                    // When onLoad was called and url was deleted in `loading`
                    this.manager.itemError( url );
                    throw err;

                }

                delete loading[ url ];

                for ( let i = 0, il = callbacks.length; i < il; i ++ ) {

                    const callback = callbacks[ i ];
                    if ( callback.onError ) callback.onError( err );

                }

                this.manager.itemError( url );

            } )
            .finally( () => {

                this.manager.itemEnd( url );

            } );

        this.manager.itemStart( url );

    }
setResponseType(value: "" | "arraybuffer" | "blob" | "document" | "json"): FileLoader
Code
setResponseType( value ) {

        this.responseType = value;
        return this;

    }
setMimeType(value: string): FileLoader
Code
setMimeType( value ) {

        this.mimeType = value;
        return this;

    }
abort(): FileLoader
Code
abort() {

        this._abortController.abort();
        this._abortController = new AbortController();

        return this;

    }