Skip to content

Commit

Permalink
Merge pull request #17 from rstgroup/requestHandler
Browse files Browse the repository at this point in the history
Request handler
  • Loading branch information
mprzodala authored Jun 22, 2022
2 parents be706bb + 00209eb commit b12fabb
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 0 deletions.
197 changes: 197 additions & 0 deletions src/RequestsHandler.test.ts
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');
});
});
});
});
126 changes: 126 additions & 0 deletions src/RequestsHandler.ts
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;
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
export { default as Eventrix } from './Eventrix';
export { default as EventsReceiver, fetchToStateReceiver, fetchStateReceiver, fetchHandler } from './EventsReceiver';
export { default as EventrixDebugger } from './EventrixDebugger';
export { default as RequestsHandler } from './RequestsHandler';

/*** REACT ***/
export {
Expand Down
16 changes: 16 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,19 @@ export enum FetchStateStatus {
Error = 'error',
Success = 'success',
}

export interface RequestI {
request: Promise<any>;
rejectHandler: EventsListenerI;
resolveHandler: EventsListenerI;
}

export interface RequestHandlerInstance {
handleRequest<RequestResponse>(request: Promise<RequestResponse>, abortEventName: string): Promise<RequestResponse>;
abortAll<RejectData>(rejectData: RejectData): void;
abortAllById<RejectData>(requestId: string, rejectData: RejectData): void;
resolveAll<ResolveData>(resolveData: ResolveData): void;
resolveAllById<ResolveData>(requestId: string, resolveData: ResolveData): void;
isAnyPending(): boolean;
isPending(requestId: string): boolean;
}

0 comments on commit b12fabb

Please sign in to comment.