Add WaitMiddleware.ts
This commit is contained in:
parent
8c23b59ecd
commit
70c94c7c63
109
packages/client/src/middleware/WaitMiddleware.test.ts
Normal file
109
packages/client/src/middleware/WaitMiddleware.test.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import {WaitMiddleware} from './WaitMiddleware'
|
||||||
|
import {
|
||||||
|
IAction, IPendingAction, IResolvedAction, IRejectedAction,
|
||||||
|
} from '../actions'
|
||||||
|
import {applyMiddleware, createStore, AnyAction} from 'redux'
|
||||||
|
import {getError} from '../test-utils'
|
||||||
|
|
||||||
|
describe('WaitMiddleware', () => {
|
||||||
|
|
||||||
|
const getStore = (wm: WaitMiddleware) => createStore(
|
||||||
|
(state: string[] = [], action: IAction<any, string>) => {
|
||||||
|
return [...state, action.type]
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
applyMiddleware(wm.handle),
|
||||||
|
)
|
||||||
|
|
||||||
|
it('waits for certain async actions to be resolved', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
const store = getStore(wm)
|
||||||
|
const promise = wm.wait(['B', 'C'])
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'A',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'B',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'C',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
await promise
|
||||||
|
expect(store.getState().slice(1)).toEqual(['A', 'B', 'C'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('times out when actions do not happen', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
const store = getStore(wm)
|
||||||
|
const promise = wm.wait(['B', 'C'], 5)
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'A',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'B',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
const error = await getError(promise)
|
||||||
|
expect(error.message).toMatch(/timed/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('errors out when a promise is rejected', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
const store = getStore(wm)
|
||||||
|
const promise = wm.wait(['B', 'C'])
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'A',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
store.dispatch({
|
||||||
|
payload: new Error('test'),
|
||||||
|
type: 'B',
|
||||||
|
status: 'rejected',
|
||||||
|
})
|
||||||
|
const error = await getError(promise)
|
||||||
|
expect(error.message).toMatch(/test/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('errors out when wait is called twice', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
const store = getStore(wm)
|
||||||
|
const promise = wm.wait(['B'])
|
||||||
|
const error = await getError(wm.wait(['B']))
|
||||||
|
expect(error.message).toMatch(/already waiting/)
|
||||||
|
store.dispatch({
|
||||||
|
payload: new Error('test'),
|
||||||
|
type: 'B',
|
||||||
|
status: 'resolved',
|
||||||
|
})
|
||||||
|
await promise
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does nothing when pending is dispatched', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
const store = getStore(wm)
|
||||||
|
const promise = wm.wait(['B'], 1)
|
||||||
|
store.dispatch({
|
||||||
|
payload: undefined,
|
||||||
|
type: 'B',
|
||||||
|
status: 'pending',
|
||||||
|
})
|
||||||
|
const error = await getError(promise)
|
||||||
|
expect(error.message).toMatch(/timed/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('resolved immediately when no actions are defined', async () => {
|
||||||
|
const wm = new WaitMiddleware()
|
||||||
|
await wm.wait([])
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
61
packages/client/src/middleware/WaitMiddleware.ts
Normal file
61
packages/client/src/middleware/WaitMiddleware.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import {TAsyncAction} from '../actions/TAsyncAction'
|
||||||
|
import {AnyAction, Middleware} from 'redux'
|
||||||
|
|
||||||
|
export class WaitMiddleware {
|
||||||
|
protected notify?: (action: TAsyncAction<any, string>) => void
|
||||||
|
|
||||||
|
handle: Middleware = store => next => (action: AnyAction) => {
|
||||||
|
next(action)
|
||||||
|
if (this.notify && 'status' in action) {
|
||||||
|
this.notify(action as TAsyncAction<any, string>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async wait(actions: string[], timeout = 10000): Promise<void> {
|
||||||
|
if (this.notify) {
|
||||||
|
throw new Error('WaitMiddleware.wait - already waiting!')
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionsByName = actions.reduce((obj, type) => {
|
||||||
|
obj[type] = true
|
||||||
|
return obj
|
||||||
|
}, {} as Record<string, boolean>)
|
||||||
|
// no duplicates here so we cannot use actions.length
|
||||||
|
let count = Object.keys(actionsByName).length
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!actions.length) {
|
||||||
|
resolve()
|
||||||
|
this.notify = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
reject(new Error('WaitMiddleware.wait - timed out!'))
|
||||||
|
this.notify = undefined
|
||||||
|
}, timeout)
|
||||||
|
|
||||||
|
this.notify = (action: TAsyncAction<any, string>) => {
|
||||||
|
if (!actionsByName[action.type]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch (action.status) {
|
||||||
|
case 'pending':
|
||||||
|
return
|
||||||
|
case 'resolved':
|
||||||
|
actionsByName[action.type] = false
|
||||||
|
count--
|
||||||
|
if (count === 0) {
|
||||||
|
resolve()
|
||||||
|
this.notify = undefined
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case 'rejected':
|
||||||
|
reject(action.payload)
|
||||||
|
this.notify = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user