Skip to content

Commit 6685c12

Browse files
committed
Merge pull request #133 from optimizely/jordan/fix-subsequent-dispatches
Allow subsequent dispatches to work after store throws error
2 parents 31e51e1 + 1042620 commit 6685c12

File tree

2 files changed

+110
-14
lines changed

2 files changed

+110
-14
lines changed

src/reactor.js

+31-12
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,26 @@ class Reactor {
117117
}
118118

119119
var prevState = this.state
120-
this.state = this.__handleAction(prevState, actionType, payload)
120+
121+
try {
122+
this.state = this.__handleAction(prevState, actionType, payload)
123+
} catch (e) {
124+
this.__isDispatching = false
125+
throw e
126+
}
127+
121128

122129
if (this.__batchDepth > 0) {
123130
this.__batchDispatchCount++
124-
} else if (this.state !== prevState) {
125-
this.__notify()
131+
} else {
132+
if (this.state !== prevState) {
133+
try {
134+
this.__notify()
135+
} catch (e) {
136+
this.__isDispatching = false
137+
throw e
138+
}
139+
}
126140
this.__isDispatching = false
127141
}
128142
}
@@ -244,7 +258,6 @@ class Reactor {
244258
this.__changeObserver.notifyObservers(this.state)
245259
}
246260

247-
248261
/**
249262
* Reduces the current state to the new state given actionType / message
250263
* @param {string} actionType
@@ -257,22 +270,23 @@ class Reactor {
257270
logging.dispatchStart(actionType, payload)
258271
}
259272

260-
// let each core handle the message
273+
// let each store handle the message
261274
this.__stores.forEach((store, id) => {
262275
var currState = state.get(id)
263276
var newState
264-
var dispatchError
265277

266278
try {
267279
newState = store.handle(currState, actionType, payload)
268280
} catch(e) {
269-
dispatchError = e
281+
// ensure console.group is properly closed
282+
logging.dispatchError(e.message)
283+
throw e
270284
}
271285

272-
if (this.debug && (newState === undefined || dispatchError)) {
273-
var error = dispatchError || 'Store handler must return a value, did you forget a return statement'
274-
logging.dispatchError(error)
275-
throw new Error(error)
286+
if (this.debug && newState === undefined) {
287+
var errorMsg = 'Store handler must return a value, did you forget a return statement'
288+
logging.dispatchError(errorMsg)
289+
throw new Error(errorMsg)
276290
}
277291

278292
state.set(id, newState)
@@ -299,7 +313,12 @@ class Reactor {
299313
if (this.__batchDispatchCount > 0) {
300314
// set to true to catch if dispatch called from observer
301315
this.__isDispatching = true
302-
this.__notify()
316+
try {
317+
this.__notify()
318+
} catch (e) {
319+
this.__isDispatching = false
320+
throw e
321+
}
303322
this.__isDispatching = false
304323
}
305324
this.__batchDispatchCount = 0

tests/reactor-tests.js

+79-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ describe('Reactor', () => {
2929
},
3030

3131
initialize() {
32+
this.on('storeError', (state, payload) => {
33+
throw new Error('Store Error')
34+
})
3235
this.on('addItem', (state, payload) => {
3336
return state.update('all', items => {
3437
return items.push(Map({
@@ -198,6 +201,36 @@ describe('Reactor', () => {
198201
expect(() => checkoutActions.setTaxPercent(5)).not.toThrow(
199202
new Error('Dispatch may not be called while a dispatch is in progress'))
200203
})
204+
205+
it('should allow subsequent dispatches if a store throws an error', () => {
206+
try {
207+
reactor.dispatch('storeError')
208+
} catch (e) {} // eslint-disable-line
209+
210+
expect(() => reactor.dispatch('setTax', 5)).not.toThrow()
211+
})
212+
213+
it('should allow subsequent dispatches if a dispatched action doesnt cause state change', () => {
214+
reactor.dispatch('noop')
215+
216+
expect(() => reactor.dispatch('setTax', 5)).not.toThrow()
217+
})
218+
219+
it('should allow subsequent dispatches if an observer throws an error', () => {
220+
var unWatchFn = reactor.observe([], state => {
221+
throw new Error('observer error')
222+
})
223+
224+
try {
225+
checkoutActions.setTaxPercent(1)
226+
} catch (e) {} // eslint-disable-line
227+
228+
unWatchFn()
229+
230+
expect(() => {
231+
checkoutActions.setTaxPercent(2)
232+
}).not.toThrow()
233+
})
201234
}) // when dispatching a relevant action
202235

203236
describe('#observe', () => {
@@ -500,8 +533,8 @@ describe('Reactor', () => {
500533
it('should log and throw an error', function() {
501534
expect(function() {
502535
reactor.dispatch('set', 'foo')
503-
}).toThrow()
504-
expect(logging.dispatchError).toHaveBeenCalled()
536+
}).toThrow(new Error('Error during action handling'))
537+
expect(logging.dispatchError).toHaveBeenCalledWith('Error during action handling')
505538
})
506539
})
507540

@@ -976,6 +1009,9 @@ describe('Reactor', () => {
9761009
},
9771010
initialize() {
9781011
this.on('add', (state, item) => state.push(toImmutable(item)))
1012+
this.on('error', (state, payload) => {
1013+
throw new Error('store error');
1014+
})
9791015
},
9801016
}),
9811017
})
@@ -1066,5 +1102,46 @@ describe('Reactor', () => {
10661102
}).not.toThrow(
10671103
new Error('Dispatch may not be called while a dispatch is in progress'))
10681104
})
1105+
1106+
it('should allow subsequent dispatches if an error is raised by a store handler', () => {
1107+
expect(() => {
1108+
reactor.batch(() => {
1109+
reactor.dispatch('add', 'one')
1110+
reactor.dispatch('error')
1111+
})
1112+
}).toThrow(new Error('store error'))
1113+
1114+
expect(() => {
1115+
reactor.dispatch('add', 'three')
1116+
}).not.toThrow()
1117+
})
1118+
1119+
it('should allow subsequent dispatches if batched action doesnt cause state change', () => {
1120+
reactor.batch(() => {
1121+
reactor.dispatch('noop')
1122+
})
1123+
1124+
expect(() => reactor.dispatch('add', 'one')).not.toThrow()
1125+
})
1126+
1127+
it('should allow subsequent dispatches if an error is raised in an observer', () => {
1128+
var unWatchFn = reactor.observe([], state => {
1129+
throw new Error('observe error')
1130+
})
1131+
1132+
expect(() => {
1133+
reactor.batch(() => {
1134+
reactor.dispatch('add', 'one')
1135+
reactor.dispatch('add', 'two')
1136+
})
1137+
}).toThrow(
1138+
new Error('observe error'))
1139+
1140+
unWatchFn()
1141+
1142+
expect(() => {
1143+
reactor.dispatch('add', 'three')
1144+
}).not.toThrow()
1145+
})
10691146
})
10701147
})

0 commit comments

Comments
 (0)