فهرست منبع

Use Fetch in FileLoader (#22510)

* initial working draft

* no progress? no problem?

* credentials & progress sorted.

* length logic

* new loadAsync function. load behaviour unchanged.

* erroneous self

* catching errors nicely.

* fix spaces

* remove todo
DefinitelyMaybe 3 سال پیش
والد
کامیت
8b80974628
1فایلهای تغییر یافته به همراه89 افزوده شده و 152 حذف شده
  1. 89 152
      src/loaders/FileLoader.js

+ 89 - 152
src/loaders/FileLoader.js

@@ -19,19 +19,17 @@ class FileLoader extends Loader {
 
 		url = this.manager.resolveURL( url );
 
-		const scope = this;
-
 		const cached = Cache.get( url );
 
 		if ( cached !== undefined ) {
 
-			scope.manager.itemStart( url );
+			this.manager.itemStart( url );
 
-			setTimeout( function () {
+			setTimeout( () => {
 
 				if ( onLoad ) onLoad( cached );
 
-				scope.manager.itemEnd( url );
+				this.manager.itemEnd( url );
 
 			}, 0 );
 
@@ -55,225 +53,164 @@ class FileLoader extends Loader {
 
 		}
 
-		// Check for data: URI
-		const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
-		const dataUriRegexResult = url.match( dataUriRegex );
-		let request;
-
-		// Safari can not handle Data URIs through XMLHttpRequest so process manually
-		if ( dataUriRegexResult ) {
-
-			const mimeType = dataUriRegexResult[ 1 ];
-			const isBase64 = !! dataUriRegexResult[ 2 ];
-
-			let data = dataUriRegexResult[ 3 ];
-			data = decodeURIComponent( data );
-
-			if ( isBase64 ) data = atob( data );
-
-			try {
-
-				let response;
-				const responseType = ( this.responseType || '' ).toLowerCase();
-
-				switch ( responseType ) {
-
-					case 'arraybuffer':
-					case 'blob':
-
-						const view = new Uint8Array( data.length );
-
-						for ( let i = 0; i < data.length; i ++ ) {
-
-							view[ i ] = data.charCodeAt( i );
-
-						}
-
-						if ( responseType === 'blob' ) {
-
-							response = new Blob( [ view.buffer ], { type: mimeType } );
+		// Initialise array for duplicate requests
+		loading[ url ] = [];
 
-						} else {
+		loading[ url ].push( {
+			onLoad: onLoad,
+			onProgress: onProgress,
+			onError: onError,
+		} );
 
-							response = view.buffer;
+		// create request
+		const req = new Request( url, {
+			headers: new Headers( this.requestHeader ),
+			credentials: this.withCredentials ? 'include' : 'same-origin',
+			// An abort controller could be added within a future PR
+		} );
 
-						}
-
-						break;
-
-					case 'document':
-
-						const parser = new DOMParser();
-						response = parser.parseFromString( data, mimeType );
-
-						break;
-
-					case 'json':
-
-						response = JSON.parse( data );
+		// start the fetch
+		fetch( req )
+			.then( response => {
 
-						break;
+				if ( response.status === 200 || response.status === 0 ) {
 
-					default: // 'text' or other
+					// Some browsers return HTTP Status 0 when using non-http protocol
+					// e.g. 'file://' or 'data://'. Handle as success.
 
-						response = data;
+					if ( response.status === 0 ) {
 
-						break;
+						console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
 
-				}
+					}
 
-				// Wait for next browser tick like standard XMLHttpRequest event dispatching does
-				setTimeout( function () {
+					const callbacks = loading[ url ];
+					const reader = response.body.getReader();
+					const contentLength = response.headers.get( 'Content-Length' );
+					const total = contentLength ? parseInt( contentLength ) : 0;
+					const lengthComputable = total !== 0;
+					let loaded = 0;
 
-					if ( onLoad ) onLoad( response );
+					// periodically read data into the new stream tracking while download progress
+					return new ReadableStream( {
+						start( controller ) {
 
-					scope.manager.itemEnd( url );
+							readData();
 
-				}, 0 );
+							function readData() {
 
-			} catch ( error ) {
+								reader.read().then( ( { done, value } ) => {
 
-				// Wait for next browser tick like standard XMLHttpRequest event dispatching does
-				setTimeout( function () {
+									if ( done ) {
 
-					if ( onError ) onError( error );
+										controller.close();
 
-					scope.manager.itemError( url );
-					scope.manager.itemEnd( url );
+									} else {
 
-				}, 0 );
+										loaded += value.byteLength;
 
-			}
+										const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
+										for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-		} else {
+											const callback = callbacks[ i ];
+											if ( callback.onProgress ) callback.onProgress( event );
 
-			// Initialise array for duplicate requests
+										}
 
-			loading[ url ] = [];
+										controller.enqueue( value );
+										readData();
 
-			loading[ url ].push( {
+									}
 
-				onLoad: onLoad,
-				onProgress: onProgress,
-				onError: onError
+								} );
 
-			} );
+							}
 
-			request = new XMLHttpRequest();
-
-			request.open( 'GET', url, true );
-
-			request.addEventListener( 'load', function ( event ) {
-
-				const response = this.response;
-
-				const callbacks = loading[ url ];
+						}
 
-				delete loading[ url ];
+					} );
 
-				if ( this.status === 200 || this.status === 0 ) {
+				} else {
 
-					// Some browsers return HTTP Status 0 when using non-http protocol
-					// e.g. 'file://' or 'data://'. Handle as success.
+					throw Error( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}` );
 
-					if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
+				}
 
-					// Add to cache only on HTTP success, so that we do not cache
-					// error response bodies as proper responses to requests.
-					Cache.add( url, response );
+			} )
+			.then( stream => {
 
-					for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+				const response = new Response( stream );
 
-						const callback = callbacks[ i ];
-						if ( callback.onLoad ) callback.onLoad( response );
+				switch ( this.responseType ) {
 
-					}
+					case 'arraybuffer':
 
-					scope.manager.itemEnd( url );
+						return response.arrayBuffer();
 
-				} else {
+					case 'blob':
 
-					for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+						return response.blob();
 
-						const callback = callbacks[ i ];
-						if ( callback.onError ) callback.onError( event );
+					case 'document':
 
-					}
+						return response.text()
+							.then( text => {
 
-					scope.manager.itemError( url );
-					scope.manager.itemEnd( url );
+								const parser = new DOMParser();
+								return parser.parseFromString( text, this.mimeType );
 
-				}
+							} );
 
-			}, false );
+					case 'json':
 
-			request.addEventListener( 'progress', function ( event ) {
+						return response.json();
 
-				const callbacks = loading[ url ];
+					default:
 
-				for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
-
-					const callback = callbacks[ i ];
-					if ( callback.onProgress ) callback.onProgress( event );
+						return response.text();
 
 				}
 
-			}, false );
+			} )
+			.then( data => {
 
-			request.addEventListener( 'error', function ( event ) {
+				// Add to cache only on HTTP success, so that we do not cache
+				// error response bodies as proper responses to requests.
+				Cache.add( 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.onError ) callback.onError( event );
+					if ( callback.onLoad ) callback.onLoad( data );
 
 				}
 
-				scope.manager.itemError( url );
-				scope.manager.itemEnd( url );
+				this.manager.itemEnd( url );
 
-			}, false );
+			} )
+			.catch( err => {
 
-			request.addEventListener( 'abort', function ( event ) {
+				// Abort errors and other errors are handled the same
 
 				const callbacks = loading[ url ];
-
 				delete loading[ url ];
 
 				for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
 					const callback = callbacks[ i ];
-					if ( callback.onError ) callback.onError( event );
+					if ( callback.onError ) callback.onError( err );
 
 				}
 
-				scope.manager.itemError( url );
-				scope.manager.itemEnd( url );
-
-			}, false );
-
-			if ( this.responseType !== undefined ) request.responseType = this.responseType;
-			if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
+				this.manager.itemError( url );
+				this.manager.itemEnd( url );
 
-			if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );
-
-			for ( const header in this.requestHeader ) {
-
-				request.setRequestHeader( header, this.requestHeader[ header ] );
-
-			}
-
-			request.send( null );
-
-		}
-
-		scope.manager.itemStart( url );
+			} );
 
-		return request;
+		this.manager.itemStart( url );
 
 	}