Home > Software engineering >  JS TS how to refactor repetitive code into a higher order function with multiple param types
JS TS how to refactor repetitive code into a higher order function with multiple param types

Time:05-24

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.

  • Related