Browse Source

Cleanup. Removed superfluous tests. Used the file property for simplicity. Tested that the new catch() is safe.

Piotrek Koszuliński 6 years ago
parent
commit
18322635ae

+ 17 - 18
packages/ckeditor5-upload/src/filerepository.js

@@ -186,19 +186,14 @@ export default class FileRepository extends Plugin {
 
 		// Store also file => loader mapping so loader can be retrieved by file instance returned upon Promise resolution.
 		if ( fileOrPromise instanceof Promise ) {
-			loader.file.then( file => {
-				this._loadersMap.set( file, loader );
-			} ).catch( () => {
-				// Catch the file promise rejection. If there are no `catch` clause, the browser
-				// will throw an error (see https://github.com/ckeditor/ckeditor5-upload/pull/90).
-				// The error will be handled by `FileLoader` so no action is required here.
-			} );
-		} else {
-			// Catch the file promise rejection. If there are no `catch` clause, the browser
-			// will throw an error (see https://github.com/ckeditor/ckeditor5-upload/pull/90).
-			loader.file.catch( () => {
-				// The error will be handled by `FileLoader` so no action is required here.
-			} );
+			loader.file
+				.then( file => {
+					this._loadersMap.set( file, loader );
+				} )
+				// Every then() must have a catch().
+				// File loader state (and rejections) are handled in read() and upload().
+				// Also, see the "does not swallow the file promise rejection" test.
+				.catch( () => {} );
 		}
 
 		loader.on( 'change:uploaded', () => {
@@ -295,7 +290,7 @@ class FileLoader {
 		/**
 		 * Additional wrapper over the initial file promise passed to this loader.
 		 *
-		 * @private
+		 * @protected
 		 * @member {module:upload/filerepository~FilePromiseWrapper}
 		 */
 		this._filePromiseWrapper = this._createFilePromiseWrapper( filePromise );
@@ -442,7 +437,7 @@ class FileLoader {
 
 		this.status = 'reading';
 
-		return this._filePromiseWrapper.promise
+		return this.file
 			.then( file => this._reader.read( file ) )
 			.then( data => {
 				// Edge case: reader was aborted after file was read - double check for proper status.
@@ -495,7 +490,7 @@ class FileLoader {
 
 		this.status = 'uploading';
 
-		return this._filePromiseWrapper.promise
+		return this.file
 			.then( () => this._adapter.upload() )
 			.then( data => {
 				this.uploadResponse = data;
@@ -521,6 +516,11 @@ class FileLoader {
 		this.status = 'aborted';
 
 		if ( !this._filePromiseWrapper.isFulfilled ) {
+			// Edge case: file loader is aborted before read() is called
+			// so it might happen that no one handled the rejection of this promise.
+			// See https://github.com/ckeditor/ckeditor5-upload/pull/100
+			this._filePromiseWrapper.promise.catch( () => {} );
+
 			this._filePromiseWrapper.rejecter( 'aborted' );
 		} else if ( status == 'reading' ) {
 			this._reader.abort();
@@ -555,7 +555,6 @@ class FileLoader {
 		const wrapper = {};
 
 		wrapper.promise = new Promise( ( resolve, reject ) => {
-			wrapper.resolver = resolve;
 			wrapper.rejecter = reject;
 			wrapper.isFulfilled = false;
 
@@ -631,9 +630,9 @@ mix( FileLoader, ObservableMixin );
  * Object returned by {@link module:upload/filerepository~FileLoader#_createFilePromiseWrapper} method
  * to add more control over the initial file promise passed to {@link module:upload/filerepository~FileLoader}.
  *
+ * @protected
  * @typedef {Object} module:upload/filerepository~FilePromiseWrapper
  * @property {Promise.<File>} promise Wrapper promise which can be chained for further processing.
- * @property {Function} resolver Resolves the promise when called.
  * @property {Function} rejecter Rejects the promise when called.
  * @property {Boolean} isFulfilled Whether original promise is already fulfilled.
  */

+ 15 - 16
packages/ckeditor5-upload/tests/filerepository.js

@@ -199,22 +199,22 @@ describe( 'FileRepository', () => {
 			expect( fileRepository.uploadedPercent ).to.equal( 20 );
 		} );
 
-		it( 'should catch if file promise rejected (file)', () => {
-			const catchSpy = sinon.spy( Promise.prototype, 'catch' );
-
-			fileRepository.createLoader( createNativeFileMock() );
-
-			// One call originates from `loader._createFilePromiseWrapper()` and other from `fileRepository.createLoader()`.
-			expect( catchSpy.callCount ).to.equal( 2 );
-		} );
-
-		it( 'should catch if file promise rejected (promise)', () => {
-			const catchSpy = sinon.spy( Promise.prototype, 'catch' );
-
-			fileRepository.createLoader( new Promise( () => {} ) );
+		// This is a test for a super edge case when a file promise was rejected,
+		// but no one called read() or upload() yet. In this case we want to be sure
+		// that we did not swallow this file promise rejection somewhere in createLoader().
+		it( 'does not swallow the file promise rejection', done => {
+			let fileRejecter;
+			const fileMock = createNativeFileMock();
+			const filePromise = new Promise( ( resolve, reject ) => {
+				fileRejecter = reject;
+			} );
+
+			const loader = fileRepository.createLoader( filePromise );
+			loader.file.catch( () => {
+				done();
+			} );
 
-			// One call originates from `loader._createFilePromiseWrapper()` and other from `fileRepository.createLoader()`.
-			expect( catchSpy.callCount ).to.equal( 2 );
+			fileRejecter( fileMock );
 		} );
 	} );
 
@@ -340,7 +340,6 @@ describe( 'FileRepository', () => {
 			it( 'should initialize filePromiseWrapper', () => {
 				expect( loader._filePromiseWrapper ).to.not.be.null;
 				expect( loader._filePromiseWrapper.promise ).to.be.instanceOf( Promise );
-				expect( loader._filePromiseWrapper.resolver ).to.be.instanceOf( Function );
 				expect( loader._filePromiseWrapper.rejecter ).to.be.instanceOf( Function );
 				expect( loader._filePromiseWrapper.isFulfilled ).to.be.false;
 			} );