Przeglądaj źródła

Introduced error rethrowing from `EmitterMixin#fire()`.

Maciej Bukowski 6 lat temu
rodzic
commit
854776c584

+ 48 - 43
packages/ckeditor5-utils/src/emittermixin.js

@@ -13,6 +13,7 @@ import priorities from './priorities';
 
 // To check if component is loaded more than once.
 import './version';
+import CKEditorError from './ckeditorerror';
 
 const _listeningTo = Symbol( 'listeningTo' );
 const _emitterId = Symbol( 'emitterId' );
@@ -184,58 +185,62 @@ const EmitterMixin = {
 	 * @inheritDoc
 	 */
 	fire( eventOrInfo, ...args ) {
-		const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo( this, eventOrInfo );
-		const event = eventInfo.name;
-		let callbacks = getCallbacksForEvent( this, event );
-
-		// Record that the event passed this emitter on its path.
-		eventInfo.path.push( this );
-
-		// Handle event listener callbacks first.
-		if ( callbacks ) {
-			// Arguments passed to each callback.
-			const callbackArgs = [ eventInfo, ...args ];
-
-			// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
-			// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
-			// failed if callbacks were added to the queue before currently processed callback.
-			// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
-			// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
-			callbacks = Array.from( callbacks );
+		try {
+			const eventInfo = eventOrInfo instanceof EventInfo ? eventOrInfo : new EventInfo( this, eventOrInfo );
+			const event = eventInfo.name;
+			let callbacks = getCallbacksForEvent( this, event );
+
+			// Record that the event passed this emitter on its path.
+			eventInfo.path.push( this );
+
+			// Handle event listener callbacks first.
+			if ( callbacks ) {
+				// Arguments passed to each callback.
+				const callbackArgs = [ eventInfo, ...args ];
+
+				// Copying callbacks array is the easiest and most secure way of preventing infinite loops, when event callbacks
+				// are added while processing other callbacks. Previous solution involved adding counters (unique ids) but
+				// failed if callbacks were added to the queue before currently processed callback.
+				// If this proves to be too inefficient, another method is to change `.on()` so callbacks are stored if same
+				// event is currently processed. Then, `.fire()` at the end, would have to add all stored events.
+				callbacks = Array.from( callbacks );
+
+				for ( let i = 0; i < callbacks.length; i++ ) {
+					callbacks[ i ].callback.apply( this, callbackArgs );
+
+					// Remove the callback from future requests if off() has been called.
+					if ( eventInfo.off.called ) {
+						// Remove the called mark for the next calls.
+						delete eventInfo.off.called;
+
+						removeCallback( this, event, callbacks[ i ].callback );
+					}
 
-			for ( let i = 0; i < callbacks.length; i++ ) {
-				callbacks[ i ].callback.apply( this, callbackArgs );
+					// Do not execute next callbacks if stop() was called.
+					if ( eventInfo.stop.called ) {
+						break;
+					}
+				}
+			}
 
-				// Remove the callback from future requests if off() has been called.
-				if ( eventInfo.off.called ) {
-					// Remove the called mark for the next calls.
-					delete eventInfo.off.called;
+			// Delegate event to other emitters if needed.
+			if ( this._delegations ) {
+				const destinations = this._delegations.get( event );
+				const passAllDestinations = this._delegations.get( '*' );
 
-					removeCallback( this, event, callbacks[ i ].callback );
+				if ( destinations ) {
+					fireDelegatedEvents( destinations, eventInfo, args );
 				}
 
-				// Do not execute next callbacks if stop() was called.
-				if ( eventInfo.stop.called ) {
-					break;
+				if ( passAllDestinations ) {
+					fireDelegatedEvents( passAllDestinations, eventInfo, args );
 				}
 			}
-		}
-
-		// Delegate event to other emitters if needed.
-		if ( this._delegations ) {
-			const destinations = this._delegations.get( event );
-			const passAllDestinations = this._delegations.get( '*' );
-
-			if ( destinations ) {
-				fireDelegatedEvents( destinations, eventInfo, args );
-			}
 
-			if ( passAllDestinations ) {
-				fireDelegatedEvents( passAllDestinations, eventInfo, args );
-			}
+			return eventInfo.return;
+		} catch ( err ) {
+			CKEditorError.rethrowUnexpectedError( err, this );
 		}
-
-		return eventInfo.return;
 	},
 
 	/**

+ 31 - 0
packages/ckeditor5-utils/tests/emittermixin.js

@@ -5,6 +5,8 @@
 
 import { default as EmitterMixin, _getEmitterListenedTo, _getEmitterId, _setEmitterId } from '../src/emittermixin';
 import EventInfo from '../src/eventinfo';
+import { expectToThrowCKEditorError } from './_utils/utils';
+import CKEditorError from '../src/ckeditorerror';
 
 describe( 'EmitterMixin', () => {
 	let emitter, listener;
@@ -154,6 +156,35 @@ describe( 'EmitterMixin', () => {
 			sinon.assert.calledThrice( spyFoo2 );
 		} );
 
+		it( 'should rethrow the CKEditorError error', () => {
+			emitter.on( 'test', () => {
+				throw new CKEditorError( 'Foo', null );
+			} );
+
+			expectToThrowCKEditorError( () => {
+				emitter.fire( 'test' );
+			}, /Foo/, null );
+		} );
+
+		it( 'should wrap an error into the CKEditorError if a native error was thrown', () => {
+			const error = new Error( 'foo' );
+			error.stack = 'bar';
+
+			emitter.on( 'test', () => {
+				throw error;
+			} );
+
+			expectToThrowCKEditorError( () => {
+				emitter.fire( 'test' );
+			}, /unexpected-error/, emitter, {
+				originalError: {
+					message: 'foo',
+					stack: 'bar',
+					name: 'Error'
+				}
+			} );
+		} );
+
 		describe( 'return value', () => {
 			it( 'is undefined by default', () => {
 				expect( emitter.fire( 'foo' ) ).to.be.undefined;