accounts-frontend/packages/app/services/request/PromiseMiddlewareLayer.ts
2020-05-24 02:08:24 +03:00

80 lines
2.7 KiB
TypeScript

import { Options } from './request';
type Action = 'catch' | 'then' | 'before';
export interface MiddlewareRequestOptions {
url: string;
options: Options;
}
export interface Middleware {
before?: (options: MiddlewareRequestOptions) => Promise<MiddlewareRequestOptions>;
then?: (resp: any, options: MiddlewareRequestOptions) => Promise<any>;
catch?: (resp: any, options: MiddlewareRequestOptions, restart: () => Promise<any>) => Promise<any>;
}
/**
* A class to handle middleware layer
*/
class PromiseMiddlewareLayer {
private middlewares: Middleware[] = [];
/**
* Adds middleware into layer.
*
* `middleware` is an object, that may have multiple keys of type function.
* Each key is a name of an action that may be passed through the layer. Each
* function should return a Promise. An action with name catch has a special
* meaning: it will be invoked as catch callback on Promise (for middlewares,
* that should handle errors)
*
* @param {object} middleware
*/
add(middleware: Middleware) {
if (typeof middleware !== 'object') {
throw new Error('A middleware must be an object');
}
if (!this.middlewares.some((mdware) => mdware === middleware)) {
this.middlewares.push(middleware);
}
}
/**
* @param {string} action - the name of middleware's hook
* @param {object} data - the initial data to pass through middlewares chain
* @param {Function} restart - a function to restart current request (for `catch` hook)
*
* @returns {Promise}
*/
run(
action: 'before',
options: MiddlewareRequestOptions,
arg3?: never,
arg4?: never,
): Promise<MiddlewareRequestOptions>;
run(action: 'then', resp: any, options: MiddlewareRequestOptions, arg4?: never): Promise<any>;
run(action: 'catch', resp: any, options: MiddlewareRequestOptions, restart: () => Promise<any>): Promise<any>;
run(action: Action, data: any, ...rest: any) {
const promiseMethod = action === 'catch' ? 'catch' : 'then';
return this.middlewares
.filter((middleware) => middleware[action])
.reduce(
(promise: Promise<any>, middleware) =>
invoke(promise, promiseMethod)((resp: any) => invoke(middleware, action)(resp, ...rest)),
invoke(Promise, action === 'catch' ? 'reject' : 'resolve')(data),
);
}
}
function invoke(instance: Record<string, any>, method: string) {
if (typeof instance[method] !== 'function') {
throw new Error(`Can not invoke ${method} on ${instance}`);
}
return (...args: any) => instance[method](...args);
}
export default PromiseMiddlewareLayer;