I've been wanting to refactor the following code into a higher order function with typescript to make it cleaner and more re-usable, though have been finding it really challenging to make it work.
import { DocumentDefinition, FilterQuery, QueryOptions, UpdateQuery } from 'mongoose';
import TaskModel, { TaskDocument } from '../models/Task.model';
import { databaseResponseTimeHistogram } from '../utils/appmetrics';
export async function createTask(
input: DocumentDefinition<
Omit<TaskDocument, 'createdAt' | 'updatedAt' | 'taskId' | 'isCompleted'>
>
) {
const metricsLabels = { operation: 'createTask' };
const timer = databaseResponseTimeHistogram.startTimer();
try {
const result = TaskModel.create(input);
timer({ ...metricsLabels, success: 'true' });
return result;
} catch (err: any) {
timer({ ...metricsLabels, success: 'false' });
throw new Error(err.message);
}
}
export async function findTask(
query: FilterQuery<TaskDocument>,
options: QueryOptions = { lean: true }
) {
const metricsLabels = { operation: 'findTask' };
const timer = databaseResponseTimeHistogram.startTimer();
try {
const result = TaskModel.findOne(query, {}, options);
timer({ ...metricsLabels, success: 'true' });
return result;
} catch (err: any) {
timer({ ...metricsLabels, success: 'false' });
throw new Error(err.message);
}
}
export async function findAndUpdateTask(
query: FilterQuery<TaskDocument>,
update: UpdateQuery<TaskDocument>,
options: QueryOptions
) {
const metricsLabels = { operation: 'findTask' };
const timer = databaseResponseTimeHistogram.startTimer();
try {
const result = TaskModel.findOneAndUpdate(query, update, options);
timer({ ...metricsLabels, success: 'true' });
return result;
} catch (err: any) {
timer({ ...metricsLabels, success: 'false' });
throw new Error(err.message);
}
}
Basically I am wanting to refactor the whole metrics functionality with the try catch block into a utility function, allowing to call it with the respective parameters, the operation, the TaskModel.method and corresponding params with would be (input) for create, (query, {}, options) for findOne and (query, update, options) for findManyAndUpdate...
So far have I ran into difficulties with correct typing of all the different params etc.
CodePudding user response:
So basically you want to refactor this part:
try {
/* SOME OPERATION */
timer({ ...metricsLabels, success: 'true' });
return result;
} catch (err: any) {
timer({ ...metricsLabels, success: 'false' });
throw new Error(err.message);
}
You can just wrap it in a function and pass in the SOME OPERATION
as an argument:
function withTimer (label:string, operation:Function) {
const metricsLabels = { operation: label };
const timer = databaseResponseTimeHistogram.startTimer();
try {
const result = operation();
timer({ ...metricsLabels, success: 'true' });
return result;
} catch (err: any) {
timer({ ...metricsLabels, success: 'false' });
throw new Error(err.message);
}
}
Now your functions can be rewritten as:
export async function createTask(
input: DocumentDefinition<
Omit<TaskDocument, 'createdAt' | 'updatedAt' | 'taskId' | 'isCompleted'>
>
) {
return withTimer('createTask',
() => TaskModel.create(input)
);
}
export async function findTask(
query: FilterQuery<TaskDocument>,
options: QueryOptions = { lean: true }
) {
return withTimer('findTask',
() => TaskModel.findOne(query, {}, options)
);
}
export async function findAndUpdateTask(
query: FilterQuery<TaskDocument>,
update: UpdateQuery<TaskDocument>,
options: QueryOptions
) {
return withTimer('findTask',
() => TaskModel.findOneAndUpdate(query, update, options)
);
}
The key to using higher-order functions is realizing that you can wrap the uncommon parts of code in functions to be passed to the common parts of code.