-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from rstgroup/requestHandler
Request handler
- Loading branch information
Showing
4 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import RequestsHandler from './RequestsHandler'; | ||
import Eventrix from './Eventrix'; | ||
|
||
describe('RequestHandler', () => { | ||
describe('normal', () => { | ||
const requestId = 'TestAbort'; | ||
const abortRequestEventName = `AbortRequest:${requestId}`; | ||
const resolveRequestEventName = `ResolveRequest:${requestId}`; | ||
let eventrix: Eventrix; | ||
let request: Promise<string>; | ||
let requestHandler: RequestsHandler; | ||
|
||
beforeEach(() => { | ||
eventrix = new Eventrix({}, []); | ||
request = new Promise<string>((resolve) => { | ||
setTimeout(() => { | ||
resolve('success'); | ||
}, 10); | ||
}); | ||
requestHandler = new RequestsHandler(eventrix); | ||
}); | ||
|
||
it('should return success when request is resolved', () => { | ||
expect.assertions(1); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
return requestPromise.then((result) => { | ||
expect(result).toEqual('success'); | ||
}); | ||
}); | ||
it('should return true when TestAbort request is pending', () => { | ||
expect.assertions(1); | ||
requestHandler.handleRequest<string>(request, requestId); | ||
expect(requestHandler.isPending(requestId)).toEqual(true); | ||
}); | ||
it('should return true when any request is pending', () => { | ||
expect.assertions(1); | ||
requestHandler.handleRequest<string>(request, requestId); | ||
expect(requestHandler.isAnyPending()).toEqual(true); | ||
}); | ||
it('should return false when all TestAbort requests are resolved', () => { | ||
expect.assertions(1); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
return requestPromise.then(() => { | ||
expect(requestHandler.isPending(requestId)).toEqual(false); | ||
}); | ||
}); | ||
it('should return false when all requests are resolved', () => { | ||
expect.assertions(1); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
return requestPromise.then(() => { | ||
expect(requestHandler.isAnyPending()).toEqual(false); | ||
}); | ||
}); | ||
it('should return error on catch when request is rejected', () => { | ||
expect.assertions(1); | ||
request = new Promise<string>((resolve, reject) => { | ||
setTimeout(() => { | ||
reject('error'); | ||
}, 10); | ||
}); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
return requestPromise.catch((result) => { | ||
expect(result).toEqual('error'); | ||
}); | ||
}); | ||
it('should register and unregister request and listeners', () => { | ||
expect.assertions(4); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
|
||
expect(eventrix.eventsEmitter.listeners[abortRequestEventName].length).toEqual(1); | ||
expect(eventrix.eventsEmitter.listeners[resolveRequestEventName].length).toEqual(1); | ||
|
||
return requestPromise.then(() => { | ||
expect(eventrix.eventsEmitter.listeners[abortRequestEventName]).toEqual(undefined); | ||
expect(eventrix.eventsEmitter.listeners[resolveRequestEventName]).toEqual(undefined); | ||
}); | ||
}); | ||
}); | ||
describe('aborted', () => { | ||
const requestId = 'TestAbort'; | ||
const requestId2 = 'TestAbort2'; | ||
const abortRequestEventName = `AbortRequest:${requestId}`; | ||
let eventrix: Eventrix; | ||
let request: Promise<string>; | ||
let request2: Promise<string>; | ||
let requestHandler: RequestsHandler; | ||
|
||
beforeEach(() => { | ||
eventrix = new Eventrix({}, []); | ||
request = new Promise<string>((resolve) => { | ||
setTimeout(() => { | ||
resolve('success'); | ||
}, 10); | ||
}); | ||
request2 = new Promise<string>((resolve) => { | ||
setTimeout(() => { | ||
resolve('success2'); | ||
}, 10); | ||
}); | ||
requestHandler = new RequestsHandler(eventrix); | ||
}); | ||
|
||
it('abortAllById -> should reject all TestAbort requests with abortedAllById response', async () => { | ||
expect.assertions(2); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId).catch((error) => error); | ||
const requestPromise2 = requestHandler.handleRequest<string>(request2, requestId).catch((error) => error); | ||
|
||
requestHandler.abortAllById<string>(requestId, 'abortedAllById'); | ||
|
||
return Promise.all([requestPromise, requestPromise2]).then(([result, result2]) => { | ||
expect(result).toEqual('abortedAllById'); | ||
expect(result2).toEqual('abortedAllById'); | ||
}); | ||
}); | ||
it('abortAll -> should reject all requests with abortedAll response', () => { | ||
expect.assertions(2); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId).catch((error) => error); | ||
const requestPromise2 = requestHandler.handleRequest<string>(request2, requestId2).catch((error) => error); | ||
|
||
requestHandler.abortAll<string>('abortedAll'); | ||
|
||
return Promise.all([requestPromise, requestPromise2]).then(([result, result2]) => { | ||
expect(result).toEqual('abortedAll'); | ||
expect(result2).toEqual('abortedAll'); | ||
}); | ||
}); | ||
it('should reject all TestAbort requests when abort event called', () => { | ||
expect.assertions(1); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
|
||
eventrix.emit(abortRequestEventName, 'abortedByEvent'); | ||
|
||
return requestPromise.catch((result) => { | ||
expect(result).toEqual('abortedByEvent'); | ||
}); | ||
}); | ||
}); | ||
describe('resolve', () => { | ||
const requestId = 'TestResolve'; | ||
const requestId2 = 'TestResolve2'; | ||
const resolveRequestEventName = `ResolveRequest:${requestId}`; | ||
let eventrix: Eventrix; | ||
let request: Promise<string>; | ||
let request2: Promise<string>; | ||
let requestHandler: RequestsHandler; | ||
|
||
beforeEach(() => { | ||
eventrix = new Eventrix({}, []); | ||
request = new Promise<string>((resolve) => { | ||
setTimeout(() => { | ||
resolve('success'); | ||
}, 10); | ||
}); | ||
request2 = new Promise<string>((resolve) => { | ||
setTimeout(() => { | ||
resolve('success2'); | ||
}, 10); | ||
}); | ||
requestHandler = new RequestsHandler(eventrix); | ||
}); | ||
|
||
it('resolveAllById -> should reject all TestResolve requests with resolvedAllById response', async () => { | ||
expect.assertions(2); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId).catch((error) => error); | ||
const requestPromise2 = requestHandler.handleRequest<string>(request2, requestId).catch((error) => error); | ||
|
||
requestHandler.resolveAllById<string>(requestId, 'resolvedAllById'); | ||
|
||
return Promise.all([requestPromise, requestPromise2]).then(([result, result2]) => { | ||
expect(result).toEqual('resolvedAllById'); | ||
expect(result2).toEqual('resolvedAllById'); | ||
}); | ||
}); | ||
it('resolveAll -> should reject all requests with resolvedAll response', () => { | ||
expect.assertions(2); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId).catch((error) => error); | ||
const requestPromise2 = requestHandler.handleRequest<string>(request2, requestId2).catch((error) => error); | ||
|
||
requestHandler.resolveAll<string>('resolvedAll'); | ||
|
||
return Promise.all([requestPromise, requestPromise2]).then(([result, result2]) => { | ||
expect(result).toEqual('resolvedAll'); | ||
expect(result2).toEqual('resolvedAll'); | ||
}); | ||
}); | ||
it('should resolve all TestResolve requests when resolve event called', () => { | ||
expect.assertions(1); | ||
const requestPromise = requestHandler.handleRequest<string>(request, requestId); | ||
|
||
eventrix.emit(resolveRequestEventName, 'resolvedByEvent'); | ||
|
||
return requestPromise.then((result) => { | ||
expect(result).toEqual('resolvedByEvent'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { EventrixI, RequestI, EventsListenerI, RequestHandlerInstance } from './interfaces'; | ||
|
||
class RequestsHandler implements RequestHandlerInstance { | ||
private eventrix: EventrixI; | ||
private requests: { | ||
[key: string]: RequestI[]; | ||
}; | ||
constructor(eventrix: EventrixI) { | ||
this.eventrix = eventrix; | ||
this.requests = {}; | ||
} | ||
private getEventNames(requestId: string) { | ||
const abortEventName = `AbortRequest:${requestId}`; | ||
const resolveEventName = `ResolveRequest:${requestId}`; | ||
return { abortEventName, resolveEventName }; | ||
} | ||
private registerRequest( | ||
request: Promise<any>, | ||
requestId: string, | ||
rejectHandler: EventsListenerI, | ||
resolveHandler: EventsListenerI, | ||
): void { | ||
const { abortEventName, resolveEventName } = this.getEventNames(requestId); | ||
|
||
if (!Array.isArray(this.requests[requestId])) { | ||
this.requests[requestId] = []; | ||
} | ||
this.eventrix.listen(abortEventName, rejectHandler); | ||
this.eventrix.listen(resolveEventName, resolveHandler); | ||
const requestHandlers = { request, rejectHandler, resolveHandler }; | ||
this.requests[requestId].push(requestHandlers); | ||
} | ||
private hasRegisteredRequest(request: Promise<any>, requestId: string): boolean { | ||
if (Array.isArray(this.requests[requestId])) { | ||
const requestHandlers = this.requests[requestId].find((requestHandler) => requestHandler.request === request); | ||
return !!requestHandlers; | ||
} | ||
return false; | ||
} | ||
private unregisterRequest(request: Promise<any>, requestId: string): void { | ||
const { abortEventName, resolveEventName } = this.getEventNames(requestId); | ||
if (Array.isArray(this.requests[requestId])) { | ||
const requestHandlers = this.requests[requestId].find((requestHandler) => requestHandler.request === request); | ||
if (requestHandlers) { | ||
this.requests[requestId] = this.requests[requestId].filter( | ||
(requestHandlersItem) => requestHandlersItem.request !== requestHandlers.request, | ||
); | ||
const { rejectHandler, resolveHandler } = requestHandlers; | ||
this.eventrix.unlisten(abortEventName, rejectHandler); | ||
this.eventrix.unlisten(resolveEventName, resolveHandler); | ||
if (this.requests[requestId].length < 1) { | ||
delete this.requests[requestId]; | ||
} | ||
} | ||
} | ||
} | ||
handleRequest<RequestResponse>(request: Promise<RequestResponse>, requestId: string): Promise<RequestResponse> { | ||
return new Promise((resolve, reject) => { | ||
const rejectHandler = (eventData: any) => { | ||
reject(eventData); | ||
}; | ||
const resolveHandler = (eventData: RequestResponse) => { | ||
resolve(eventData); | ||
}; | ||
|
||
this.registerRequest(request, requestId, rejectHandler, resolveHandler); | ||
request | ||
.then((response) => { | ||
if (this.hasRegisteredRequest(request, requestId)) { | ||
this.unregisterRequest(request, requestId); | ||
resolve(response); | ||
} | ||
}) | ||
.catch((error) => { | ||
if (this.hasRegisteredRequest(request, requestId)) { | ||
this.unregisterRequest(request, requestId); | ||
reject(error); | ||
} | ||
}); | ||
}); | ||
} | ||
abortAll<RejectData>(rejectData: RejectData): void { | ||
const requestIds = Object.keys(this.requests); | ||
requestIds.forEach((requestId) => { | ||
this.abortAllById<RejectData>(requestId, rejectData); | ||
}); | ||
} | ||
resolveAll<ResolveData>(resolveData: ResolveData): void { | ||
const requestIds = Object.keys(this.requests); | ||
requestIds.forEach((requestId) => { | ||
this.resolveAllById<ResolveData>(requestId, resolveData); | ||
}); | ||
} | ||
abortAllById<RejectData>(requestId: string, rejectData: RejectData): void { | ||
const { abortEventName } = this.getEventNames(requestId); | ||
if (Array.isArray(this.requests[requestId])) { | ||
this.eventrix.emit<RejectData>(abortEventName, rejectData); | ||
this.requests[requestId].forEach((requestHandler) => { | ||
this.unregisterRequest(requestHandler.request, requestId); | ||
}); | ||
} | ||
} | ||
resolveAllById<ResolveData>(requestId: string, resolveData: ResolveData): void { | ||
const { resolveEventName } = this.getEventNames(requestId); | ||
if (Array.isArray(this.requests[requestId])) { | ||
this.eventrix.emit<ResolveData>(resolveEventName, resolveData); | ||
this.requests[requestId].forEach((requestHandler) => { | ||
this.unregisterRequest(requestHandler.request, requestId); | ||
}); | ||
} | ||
} | ||
isAnyPending(): boolean { | ||
let hasPendingRequests = false; | ||
Object.keys(this.requests).forEach((requestId) => { | ||
if (this.isPending(requestId)) { | ||
hasPendingRequests = true; | ||
} | ||
}); | ||
return hasPendingRequests; | ||
} | ||
isPending(requestId: string): boolean { | ||
return Array.isArray(this.requests[requestId]) && this.requests[requestId].length > 0; | ||
} | ||
} | ||
|
||
export default RequestsHandler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters