FileLoader.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. import { Cache } from './Cache.js';
  2. import { Loader } from './Loader.js';
  3. const loading = {};
  4. class HttpError extends Error {
  5. constructor( message, response ) {
  6. super( message );
  7. this.response = response;
  8. }
  9. }
  10. class FileLoader extends Loader {
  11. constructor( manager ) {
  12. super( manager );
  13. }
  14. load( url, onLoad, onProgress, onError ) {
  15. if ( url === undefined ) url = '';
  16. if ( this.path !== undefined ) url = this.path + url;
  17. url = this.manager.resolveURL( url );
  18. const cached = Cache.get( url );
  19. if ( cached !== undefined ) {
  20. this.manager.itemStart( url );
  21. setTimeout( () => {
  22. if ( onLoad ) onLoad( cached );
  23. this.manager.itemEnd( url );
  24. }, 0 );
  25. return cached;
  26. }
  27. // Check if request is duplicate
  28. if ( loading[ url ] !== undefined ) {
  29. loading[ url ].push( {
  30. onLoad: onLoad,
  31. onProgress: onProgress,
  32. onError: onError
  33. } );
  34. return;
  35. }
  36. // Initialise array for duplicate requests
  37. loading[ url ] = [];
  38. loading[ url ].push( {
  39. onLoad: onLoad,
  40. onProgress: onProgress,
  41. onError: onError,
  42. } );
  43. // create request
  44. const req = new Request( url, {
  45. headers: new Headers( this.requestHeader ),
  46. credentials: this.withCredentials ? 'include' : 'same-origin',
  47. // An abort controller could be added within a future PR
  48. } );
  49. // record states ( avoid data race )
  50. const mimeType = this.mimeType;
  51. const responseType = this.responseType;
  52. // start the fetch
  53. fetch( req )
  54. .then( response => {
  55. if ( response.status === 200 || response.status === 0 ) {
  56. // Some browsers return HTTP Status 0 when using non-http protocol
  57. // e.g. 'file://' or 'data://'. Handle as success.
  58. if ( response.status === 0 ) {
  59. console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
  60. }
  61. // Workaround: Checking if response.body === undefined for Alipay browser #23548
  62. if ( typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined ) {
  63. return response;
  64. }
  65. const callbacks = loading[ url ];
  66. const reader = response.body.getReader();
  67. // Nginx needs X-File-Size check
  68. // https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
  69. const contentLength = response.headers.get( 'Content-Length' ) || response.headers.get( 'X-File-Size' );
  70. const total = contentLength ? parseInt( contentLength ) : 0;
  71. const lengthComputable = total !== 0;
  72. let loaded = 0;
  73. // periodically read data into the new stream tracking while download progress
  74. const stream = new ReadableStream( {
  75. start( controller ) {
  76. readData();
  77. function readData() {
  78. reader.read().then( ( { done, value } ) => {
  79. if ( done ) {
  80. controller.close();
  81. } else {
  82. loaded += value.byteLength;
  83. const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
  84. for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
  85. const callback = callbacks[ i ];
  86. if ( callback.onProgress ) callback.onProgress( event );
  87. }
  88. controller.enqueue( value );
  89. readData();
  90. }
  91. } );
  92. }
  93. }
  94. } );
  95. return new Response( stream );
  96. } else {
  97. throw new HttpError( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response );
  98. }
  99. } )
  100. .then( response => {
  101. switch ( responseType ) {
  102. case 'arraybuffer':
  103. return response.arrayBuffer();
  104. case 'blob':
  105. return response.blob();
  106. case 'document':
  107. return response.text()
  108. .then( text => {
  109. const parser = new DOMParser();
  110. return parser.parseFromString( text, mimeType );
  111. } );
  112. case 'json':
  113. return response.json();
  114. default:
  115. if ( mimeType === undefined ) {
  116. return response.text();
  117. } else {
  118. // sniff encoding
  119. const re = /charset="?([^;"\s]*)"?/i;
  120. const exec = re.exec( mimeType );
  121. const label = exec && exec[ 1 ] ? exec[ 1 ].toLowerCase() : undefined;
  122. const decoder = new TextDecoder( label );
  123. return response.arrayBuffer().then( ab => decoder.decode( ab ) );
  124. }
  125. }
  126. } )
  127. .then( data => {
  128. // Add to cache only on HTTP success, so that we do not cache
  129. // error response bodies as proper responses to requests.
  130. Cache.add( url, data );
  131. const callbacks = loading[ url ];
  132. delete loading[ url ];
  133. for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
  134. const callback = callbacks[ i ];
  135. if ( callback.onLoad ) callback.onLoad( data );
  136. }
  137. } )
  138. .catch( err => {
  139. // Abort errors and other errors are handled the same
  140. const callbacks = loading[ url ];
  141. if ( callbacks === undefined ) {
  142. // When onLoad was called and url was deleted in `loading`
  143. this.manager.itemError( url );
  144. throw err;
  145. }
  146. delete loading[ url ];
  147. for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
  148. const callback = callbacks[ i ];
  149. if ( callback.onError ) callback.onError( err );
  150. }
  151. this.manager.itemError( url );
  152. } )
  153. .finally( () => {
  154. this.manager.itemEnd( url );
  155. } );
  156. this.manager.itemStart( url );
  157. }
  158. setResponseType( value ) {
  159. this.responseType = value;
  160. return this;
  161. }
  162. setMimeType( value ) {
  163. this.mimeType = value;
  164. return this;
  165. }
  166. }
  167. export { FileLoader };
粤ICP备19079148号