Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit 40906d4

Browse files
authored
Merge pull request #100 from ckeditor/t/ckeditor5-image/312
Fix: Add missing `catch()` clauses to file loader promises.
2 parents 4f99b7d + 8e0e059 commit 40906d4

File tree

3 files changed

+39
-31
lines changed

3 files changed

+39
-31
lines changed

src/filerepository.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,16 @@ export default class FileRepository extends Plugin {
186186

187187
// Store also file => loader mapping so loader can be retrieved by file instance returned upon Promise resolution.
188188
if ( fileOrPromise instanceof Promise ) {
189-
loader.file.then( file => {
190-
this._loadersMap.set( file, loader );
191-
} );
189+
loader.file
190+
.then( file => {
191+
this._loadersMap.set( file, loader );
192+
} )
193+
// Every then() must have a catch().
194+
// File loader state (and rejections) are handled in read() and upload().
195+
// Also, see the "does not swallow the file promise rejection" test.
196+
.catch( () => {} );
192197
}
193198

194-
// Catch the file promise rejection. If there are no `catch` clause, the browser
195-
// will throw an error (see https://github.com/ckeditor/ckeditor5-upload/pull/90).
196-
loader.file.catch( () => {
197-
// The error will be handled by `FileLoader` so no action is required here.
198-
} );
199-
200199
loader.on( 'change:uploaded', () => {
201200
let aggregatedUploaded = 0;
202201

@@ -291,7 +290,7 @@ class FileLoader {
291290
/**
292291
* Additional wrapper over the initial file promise passed to this loader.
293292
*
294-
* @private
293+
* @protected
295294
* @member {module:upload/filerepository~FilePromiseWrapper}
296295
*/
297296
this._filePromiseWrapper = this._createFilePromiseWrapper( filePromise );
@@ -438,9 +437,14 @@ class FileLoader {
438437

439438
this.status = 'reading';
440439

441-
return this._filePromiseWrapper.promise
440+
return this.file
442441
.then( file => this._reader.read( file ) )
443442
.then( data => {
443+
// Edge case: reader was aborted after file was read - double check for proper status.
444+
if ( this.status !== 'reading' ) {
445+
throw this.status;
446+
}
447+
444448
this.status = 'idle';
445449

446450
return data;
@@ -486,7 +490,7 @@ class FileLoader {
486490

487491
this.status = 'uploading';
488492

489-
return this._filePromiseWrapper.promise
493+
return this.file
490494
.then( () => this._adapter.upload() )
491495
.then( data => {
492496
this.uploadResponse = data;
@@ -512,6 +516,11 @@ class FileLoader {
512516
this.status = 'aborted';
513517

514518
if ( !this._filePromiseWrapper.isFulfilled ) {
519+
// Edge case: file loader is aborted before read() is called
520+
// so it might happen that no one handled the rejection of this promise.
521+
// See https://github.com/ckeditor/ckeditor5-upload/pull/100
522+
this._filePromiseWrapper.promise.catch( () => {} );
523+
515524
this._filePromiseWrapper.rejecter( 'aborted' );
516525
} else if ( status == 'reading' ) {
517526
this._reader.abort();
@@ -546,7 +555,6 @@ class FileLoader {
546555
const wrapper = {};
547556

548557
wrapper.promise = new Promise( ( resolve, reject ) => {
549-
wrapper.resolver = resolve;
550558
wrapper.rejecter = reject;
551559
wrapper.isFulfilled = false;
552560

@@ -622,9 +630,9 @@ mix( FileLoader, ObservableMixin );
622630
* Object returned by {@link module:upload/filerepository~FileLoader#_createFilePromiseWrapper} method
623631
* to add more control over the initial file promise passed to {@link module:upload/filerepository~FileLoader}.
624632
*
633+
* @protected
625634
* @typedef {Object} module:upload/filerepository~FilePromiseWrapper
626635
* @property {Promise.<File>} promise Wrapper promise which can be chained for further processing.
627-
* @property {Function} resolver Resolves the promise when called.
628636
* @property {Function} rejecter Rejects the promise when called.
629637
* @property {Boolean} isFulfilled Whether original promise is already fulfilled.
630638
*/

tests/adapters/base64uploadadapter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ describe( 'Base64UploadAdapter', () => {
140140
const adapter = fileRepository.createLoader( createNativeFileMock() );
141141

142142
expect( () => {
143-
adapter.upload();
143+
// Catch the upload error to prevent uncaught promise errors
144+
adapter.upload().catch( () => {} );
144145
adapter.abort();
145146
} ).to.not.throw();
146147

tests/filerepository.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -199,22 +199,22 @@ describe( 'FileRepository', () => {
199199
expect( fileRepository.uploadedPercent ).to.equal( 20 );
200200
} );
201201

202-
it( 'should catch if file promise rejected (file)', () => {
203-
const catchSpy = sinon.spy( Promise.prototype, 'catch' );
204-
205-
fileRepository.createLoader( createNativeFileMock() );
206-
207-
// One call originates from `loader._createFilePromiseWrapper()` and other from `fileRepository.createLoader()`.
208-
expect( catchSpy.callCount ).to.equal( 2 );
209-
} );
210-
211-
it( 'should catch if file promise rejected (promise)', () => {
212-
const catchSpy = sinon.spy( Promise.prototype, 'catch' );
213-
214-
fileRepository.createLoader( new Promise( () => {} ) );
202+
// This is a test for a super edge case when a file promise was rejected,
203+
// but no one called read() or upload() yet. In this case we want to be sure
204+
// that we did not swallow this file promise rejection somewhere in createLoader().
205+
it( 'does not swallow the file promise rejection', done => {
206+
let fileRejecter;
207+
const fileMock = createNativeFileMock();
208+
const filePromise = new Promise( ( resolve, reject ) => {
209+
fileRejecter = reject;
210+
} );
211+
212+
const loader = fileRepository.createLoader( filePromise );
213+
loader.file.catch( () => {
214+
done();
215+
} );
215216

216-
// One call originates from `loader._createFilePromiseWrapper()` and other from `fileRepository.createLoader()`.
217-
expect( catchSpy.callCount ).to.equal( 2 );
217+
fileRejecter( fileMock );
218218
} );
219219
} );
220220

@@ -340,7 +340,6 @@ describe( 'FileRepository', () => {
340340
it( 'should initialize filePromiseWrapper', () => {
341341
expect( loader._filePromiseWrapper ).to.not.be.null;
342342
expect( loader._filePromiseWrapper.promise ).to.be.instanceOf( Promise );
343-
expect( loader._filePromiseWrapper.resolver ).to.be.instanceOf( Function );
344343
expect( loader._filePromiseWrapper.rejecter ).to.be.instanceOf( Function );
345344
expect( loader._filePromiseWrapper.isFulfilled ).to.be.false;
346345
} );

0 commit comments

Comments
 (0)