浏览代码

Merge pull request #27 from ckeditor/i/2546

Feature: Provided error handling for save actions. Closes ckeditor/ckeditor5#1546.
Piotr Jasiun 6 年之前
父节点
当前提交
edf847bf33
共有 2 个文件被更改,包括 54 次插入1 次删除
  1. 14 0
      packages/ckeditor5-autosave/src/autosave.js
  2. 40 1
      packages/ckeditor5-autosave/tests/autosave.js

+ 14 - 0
packages/ckeditor5-autosave/src/autosave.js

@@ -88,6 +88,8 @@ export default class Autosave extends Plugin {
 		 * * synchronized – When all changes are saved.
 		 * * waiting – When the plugin is waiting for other changes before calling `adapter#save()` and `config.autosave.save()`.
 		 * * saving – When the provided save method is called and the plugin waits for the response.
+		 * * error &ndash When the provided save method will throw an error. This state immediately changes to the `saving` state and
+		 * the save method will be called again in the short period of time.
 		 *
 		 * @member {'synchronized'|'waiting'|'saving'} #state
 		 */
@@ -237,6 +239,18 @@ export default class Autosave extends Plugin {
 			.then( () => Promise.all(
 				this._saveCallbacks.map( cb => cb( this.editor ) )
 			) )
+			// In case of an error re-try the save later and throw the original error.
+			// Being in the `saving` state ensures that the debounced save action
+			// won't be delayed further by the `change:data` event listener.
+			.catch( err => {
+				this.state = 'error';
+				// Change immediately to the `saving` state so the `change:state` event will be fired.
+				this.state = 'saving';
+
+				this._debouncedSave();
+
+				throw err;
+			} )
 			.then( () => {
 				if ( this.editor.model.document.version > this._lastDocumentVersion ) {
 					this.state = 'waiting';

+ 40 - 1
packages/ckeditor5-autosave/tests/autosave.js

@@ -617,13 +617,52 @@ describe( 'Autosave', () => {
 					expect( pendingActions.hasAny ).to.be.true;
 					sinon.clock.tick( 1000 );
 				} )
-				.then( () => Promise.resolve() )
+				.then( runPromiseCycles )
 				.then( () => {
 					expect( pendingActions.hasAny ).to.be.false;
 					sinon.assert.calledOnce( serverActionSpy );
 					sinon.assert.calledOnce( serverActionStub );
 				} );
 		} );
+
+		it( 'should handle a situration when the save callback throws an error', () => {
+			const pendingActions = editor.plugins.get( PendingActions );
+			const successServerActionSpy = sinon.spy();
+			const serverActionStub = sinon.stub();
+
+			serverActionStub.onFirstCall()
+				.rejects( new Error( 'foo' ) );
+
+			serverActionStub.onSecondCall()
+				.callsFake( successServerActionSpy );
+
+			autosave.adapter = {
+				save: serverActionStub
+			};
+
+			editor.model.change( writer => {
+				writer.setSelection( writer.createRangeIn( editor.model.document.getRoot().getChild( 0 ) ) );
+				editor.model.insertContent( writer.createText( 'foo' ) );
+			} );
+
+			return editor.destroy()
+				.then( () => {
+					expect( pendingActions.hasAny ).to.be.true;
+				} )
+				.then( runPromiseCycles )
+				.then( () => {
+					expect( pendingActions.hasAny ).to.be.true;
+					sinon.assert.calledOnce( serverActionStub );
+					sinon.assert.notCalled( successServerActionSpy );
+				} )
+				.then( () => sinon.clock.tick( 1000 ) )
+				.then( runPromiseCycles )
+				.then( () => {
+					expect( pendingActions.hasAny ).to.be.false;
+					sinon.assert.calledTwice( serverActionStub );
+					sinon.assert.calledOnce( successServerActionSpy );
+				} );
+		} );
 	} );
 
 	it( 'should run callbacks until the editor is in the ready state', () => {