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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { actions, reducer, emitEvent } from '../event_emit';
|
import { actions, reducer, emitEvent, emitEventWithAbort } from '../event-emit';
|
||||||
|
|
||||||
const EMIT_TYPES = {
|
const EMIT_TYPES = {
|
||||||
CHECKOUT_COMPLETE_WITH_SUCCESS: 'checkout_complete',
|
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,
|
EMIT_TYPES,
|
||||||
emitterSubscribers,
|
emitterSubscribers,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
|
emitEventWithAbort,
|
||||||
reducer as emitReducer,
|
reducer as emitReducer,
|
||||||
} from './event-emit';
|
} from './event-emit';
|
||||||
|
|
||||||
|
@ -124,23 +125,18 @@ export const CheckoutProvider = ( {
|
||||||
useEffect( () => {
|
useEffect( () => {
|
||||||
const status = checkoutState.status;
|
const status = checkoutState.status;
|
||||||
if ( status === STATUS.PROCESSING ) {
|
if ( status === STATUS.PROCESSING ) {
|
||||||
const error = emitEvent(
|
emitEventWithAbort(
|
||||||
currentObservers.current,
|
currentObservers.current,
|
||||||
EMIT_TYPES.CHECKOUT_PROCESSING,
|
EMIT_TYPES.CHECKOUT_PROCESSING,
|
||||||
{}
|
{}
|
||||||
);
|
).then( ( response ) => {
|
||||||
//@todo bail if error object detected (see flow).
|
if ( response !== true ) {
|
||||||
//Fire off checkoutFail event, and then reset checkout
|
// @todo handle any validation error property values in the
|
||||||
//status to idle (with hasError flag) - then return from this hook.
|
// response
|
||||||
// Finally after the event subscribers have processed, do the
|
dispatchActions.setHasError();
|
||||||
// checkout submit sending the order to the server for processing
|
}
|
||||||
// and followup on errors from it.
|
dispatch( actions.setComplete() );
|
||||||
// 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() );
|
|
||||||
}
|
}
|
||||||
if ( checkoutState.isComplete ) {
|
if ( checkoutState.isComplete ) {
|
||||||
if ( checkoutState.hasError ) {
|
if ( checkoutState.hasError ) {
|
||||||
|
@ -154,14 +150,21 @@ export const CheckoutProvider = ( {
|
||||||
currentObservers.current,
|
currentObservers.current,
|
||||||
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
|
EMIT_TYPES.CHECKOUT_COMPLETE_WITH_SUCCESS,
|
||||||
{}
|
{}
|
||||||
);
|
).then( () => {
|
||||||
}
|
// all observers have done their thing so let's redirect
|
||||||
// all observers have done their thing so let's redirect (if no error)
|
// (if no error)
|
||||||
if ( ! checkoutState.hasError ) {
|
if ( ! checkoutState.hasError ) {
|
||||||
window.location = checkoutState.redirectUrl;
|
window.location = checkoutState.redirectUrl;
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [ checkoutState.status, checkoutState.hasError ] );
|
}, [
|
||||||
|
checkoutState.status,
|
||||||
|
checkoutState.hasError,
|
||||||
|
checkoutState.isComplete,
|
||||||
|
checkoutState.redirectUrl,
|
||||||
|
] );
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
dispatch( actions.setProcessing() );
|
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
|
* 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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { actions, reducer, emitEvent } from '../event_emit';
|
import { actions, reducer, emitEvent } from '../event-emit';
|
||||||
|
|
||||||
const EMIT_TYPES = {
|
const EMIT_TYPES = {
|
||||||
SHIPPING_RATES_SUCCESS: 'shipping_rates_success',
|
SHIPPING_RATES_SUCCESS: 'shipping_rates_success',
|
||||||
|
|
Loading…
Reference in New Issue