Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to deduce argument types for the callback?

I have an arrow function, it is used for cached queries. It accepts cache key and a callback function with it’s arguments. At first this function looks up in Redis with the specified key, and if there is no cache by that key – it executes the callback.

Everything works, but I don’t like the way how my declaration looks like: it’s unsafe (you can pass argument of wrong type), and on the other hand I have a disabled linter warning. So here is my code (only the relevant part remaining):

import { RedisClient } from './../redis/redisClient';

export type CachedQueryPromiseResult<T> = { result?: T, error?: object };
export type CachedQueryPromise<T> = Promise<CachedQueryPromiseResult<T>>;

export const cachedDbQuery = async <T,>(
    opts: { cacheKey: string, ttlSec: number, redis: RedisClient },
    callback: (...cbArgs: any) => Promise<T>,
    ...args: any
): CachedQueryPromise<T> => {
    const response: CachedQueryPromiseResult<T> = {};

    const cache = await opts.redis.impl.get(opts.cacheKey);
    if (cache) {
        response.result = JSON.parse(cache) as T;
        return response;
    }

    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
    const result = await callback(...args);
    if (result) {
        await opts.redis.impl.setex(opts.cacheKey, opts.ttlSec, JSON.stringify(result));
        response.result = result;
    }

    return response;
};

How can I declare this callback? Now it accepts any, but I would like it to deduct the required arguments with the types. It is not safe now.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

cachedDbQuery({ redis: redisClient, cacheKey: 'TESTAR', ttlSec: 120 }, vkuClient.getLicenseInfo.bind(vkuClient), identificator)

getLicenseInfo accepts string, but with the current implementation I can pass any type to it.

What could you suggest?

>Solution :

Add another generic parameter, as TS will be able to infer this one as well:

export const cachedDbQuery = async <T, A extends ReadonlyArray<unknown>>(
    opts: { cacheKey: string, ttlSec: number, redis: RedisClient },
    callback: (...cbArgs: A) => Promise<T>,
    ...args: A
): CachedQueryPromise<T> => {

However, since this one represents the arguments, it must have a constraint of an array.

Playground (severely simplified)

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading