Convert emitters to promise (https://github.com/woocommerce/woocommerce-blocks/pull/2003)
* restructure event-emit directory and convert emitters to promises - also add emitEventWithAbort function * implement event emitters as promises * clean up logic - return from for loop - define response as const on each iteration. - return true if loop completes successfully. * rename event_emit folder to event-emit
This commit is contained in:
parent
8ca9fc9b6f
commit
e4a82aa1ce
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { actions, reducer, emitEvent } from '../event_emit';
|
||||
import { actions, reducer, emitEvent, emitEventWithAbort } from '../event-emit';
|
||||
|
||||
const EMIT_TYPES = {
|
||||
CHECKOUT_COMPLETE_WITH_SUCCESS: 'checkout_complete',
|
||||
|
@ -70,4 +70,10 @@ const emitterSubscribers = ( dispatcher ) => ( {
|
|||
},
|
||||
} );
|
||||
|
||||
export { EMIT_TYPES, emitterSubscribers, reducer, emitEvent };
|
||||
export {
|
||||
EMIT_TYPES,
|
||||
emitterSubscribers,
|
||||
reducer,
|
||||
emitEvent,
|
||||
emitEventWithAbort,
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
EMIT_TYPES,
|
||||
emitterSubscribers,
|
||||
emitEvent,
|
||||
emitEventWithAbort,
|
||||
reducer as emitReducer,
|
||||
} from './event-emit';
|
||||
|
||||
|
@ -124,23 +125,18 @@ export const CheckoutProvider = ( {
|
|||
useEffect( () => {
|
||||
const status = checkoutState.status;
|
||||
if ( status === STATUS.PROCESSING ) {
|
||||
const error = emitEvent(
|
||||
emitEventWithAbort(
|
||||
currentObservers.current,
|
||||
EMIT_TYPES.CHECKOUT_PROCESSING,
|
||||
{}
|
||||
);
|
||||
//@todo bail if error object detected (see flow).
|
||||
//Fire off checkoutFail event, and then reset checkout
|
||||
//status to idle (with hasError flag) - then return from this hook.
|
||||
// Finally after the event subscribers have processed, do the
|
||||
// checkout submit sending the order to the server for processing
|
||||
// and followup on errors from it.
|
||||
// I'll need to setup a special emitter that aborts on first fail
|
||||
// instead of hitting all subscribed event observers.
|
||||
if ( error ) {
|
||||
dispatchActions.setHasError();
|
||||
}
|
||||
dispatch( actions.setComplete() );
|
||||
).then( ( response ) => {
|
||||
if ( response !== true ) {
|
||||
// @todo handle any validation error property values in the
|
||||
// response
|
||||
dispatchActions.setHasError();
|
||||
}
|
||||
dispatch( actions.setComplete() );
|
||||
} );
|
||||
}
|
||||
if ( checkoutState.isComplete ) {
|
||||
if ( checkoutState.hasError ) {
|
||||
|
@ -154,14 +150,21 @@ export const CheckoutProvider = ( {
|
|||
currentObservers.current,
|
||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
|
||||
{}
|
||||
);
|
||||
}
|
||||
// all observers have done their thing so let's redirect (if no error)
|
||||
if ( ! checkoutState.hasError ) {
|
||||
window.location = checkoutState.redirectUrl;
|
||||
).then( () => {
|
||||
// all observers have done their thing so let's redirect
|
||||
// (if no error)
|
||||
if ( ! checkoutState.hasError ) {
|
||||
window.location = checkoutState.redirectUrl;
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}, [ checkoutState.status, checkoutState.hasError ] );
|
||||
}, [
|
||||
checkoutState.status,
|
||||
checkoutState.hasError,
|
||||
checkoutState.isComplete,
|
||||
checkoutState.redirectUrl,
|
||||
] );
|
||||
|
||||
const onSubmit = () => {
|
||||
dispatch( actions.setProcessing() );
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Emits events on registered observers for the provided type and passes along
|
||||
* the provided data.
|
||||
*
|
||||
* This event emitter will silently catch promise errors, but doesn't care
|
||||
* otherwise if any errors are caused by observers. So events that do care
|
||||
* should use `emitEventWithAbort` instead.
|
||||
*
|
||||
* @param {Object} observers The registered observers to omit to.
|
||||
* @param {string} eventType The event type being emitted.
|
||||
* @param {*} data Data passed along to the observer when it is
|
||||
* invoked.
|
||||
*
|
||||
* @return {Promise} A promise that resolves to true after all observers have
|
||||
* executed.
|
||||
*/
|
||||
export const emitEvent = async ( observers, eventType, data ) => {
|
||||
const observersByType = observers[ eventType ] || [];
|
||||
for ( let i = 0; i < observersByType.length; i++ ) {
|
||||
try {
|
||||
await Promise.resolve( observersByType[ i ]( data ) );
|
||||
} catch ( e ) {
|
||||
// we don't care about errors blocking execution, but will
|
||||
// console.error for troubleshooting.
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( e );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits events on registered observers for the provided type and passes along
|
||||
* the provided data. This event emitter will abort and return any value from
|
||||
* observers that do not return true.
|
||||
*
|
||||
* @param {Object} observers The registered observers to omit to.
|
||||
* @param {string} eventType The event type being emitted.
|
||||
* @param {*} data Data passed along to the observer when it is
|
||||
* invoked.
|
||||
*
|
||||
* @return {Promise} Returns a promise that resolves to either boolean or the
|
||||
* return value of the aborted observer.
|
||||
*/
|
||||
export const emitEventWithAbort = async ( observers, eventType, data ) => {
|
||||
const observersByType = observers[ eventType ] || [];
|
||||
for ( let i = 0; i < observersByType.length; i++ ) {
|
||||
try {
|
||||
const response = await Promise.resolve(
|
||||
observersByType[ i ]( data )
|
||||
);
|
||||
if ( response !== true ) {
|
||||
return response;
|
||||
}
|
||||
} catch ( e ) {
|
||||
// we don't handle thrown errors but just console.log for
|
||||
// troubleshooting
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( e );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
export * from './reducer';
|
||||
export * from './emitters';
|
|
@ -26,26 +26,6 @@ export const actions = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits events on registered observers for the provided type and passes along
|
||||
* the provided data.
|
||||
*
|
||||
* @param {Object} observers The registered observers to omit to.
|
||||
* @param {string} eventType The event type being emitted.
|
||||
* @param {*} data Data passed along to the observer when it is
|
||||
* invoked.
|
||||
*/
|
||||
export const emitEvent = ( observers, eventType, data ) => {
|
||||
const observersByType = observers[ eventType ] || [];
|
||||
let hasError = false;
|
||||
observersByType.forEach( ( observer ) => {
|
||||
if ( typeof observer === 'function' ) {
|
||||
hasError = !! observer( data, hasError );
|
||||
}
|
||||
} );
|
||||
return hasError;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles actions for emmitters
|
||||
*
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { emitEvent, emitEventWithAbort } from '../emitters';
|
||||
|
||||
describe( 'Testing emitters', () => {
|
||||
let observerMocks = {};
|
||||
beforeEach( () => {
|
||||
observerMocks = {
|
||||
observerA: jest.fn().mockReturnValue( true ),
|
||||
observerB: jest.fn().mockReturnValue( true ),
|
||||
observerReturnValue: jest.fn().mockReturnValue( 10 ),
|
||||
observerPromiseWithReject: jest
|
||||
.fn()
|
||||
.mockRejectedValue( 'an error' ),
|
||||
observerPromiseWithResolvedValue: jest.fn().mockResolvedValue( 10 ),
|
||||
};
|
||||
} );
|
||||
describe( 'Testing emitEvent()', () => {
|
||||
it( 'invokes all observers', async () => {
|
||||
const observers = { test: Object.values( observerMocks ) };
|
||||
const response = await emitEvent( observers, 'test', 'foo' );
|
||||
expect( console ).toHaveErroredWith( 'an error' );
|
||||
expect( observerMocks.observerA ).toHaveBeenCalledTimes( 1 );
|
||||
expect( observerMocks.observerB ).toHaveBeenCalledWith( 'foo' );
|
||||
expect( response ).toBe( true );
|
||||
} );
|
||||
} );
|
||||
describe( 'Testing emitEventWithAbort()', () => {
|
||||
it(
|
||||
'aborts on non truthy value and does not invoke remaining ' +
|
||||
'observers',
|
||||
async () => {
|
||||
const observers = { test: Object.values( observerMocks ) };
|
||||
const response = await emitEventWithAbort(
|
||||
observers,
|
||||
'test',
|
||||
'foo'
|
||||
);
|
||||
expect( console ).not.toHaveErrored();
|
||||
expect( observerMocks.observerB ).toHaveBeenCalledTimes( 1 );
|
||||
expect(
|
||||
observerMocks.observerPromiseWithResolvedValue
|
||||
).not.toHaveBeenCalled();
|
||||
expect( response ).toBe( 10 );
|
||||
}
|
||||
);
|
||||
} );
|
||||
} );
|
|
@ -1 +0,0 @@
|
|||
export * from './reducer';
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { actions, reducer, emitEvent } from '../event_emit';
|
||||
import { actions, reducer, emitEvent } from '../event-emit';
|
||||
|
||||
const EMIT_TYPES = {
|
||||
SHIPPING_RATES_SUCCESS: 'shipping_rates_success',
|
||||
|
|
Loading…
Reference in New Issue