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