skip to Main Content
// callbacks
const callback1 = (key: string, value: unknown) => {
    if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
    return value
}

const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
    if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
    if (value < min || value > max) throw new Error('error')
    return value
}

const callback3 = (key: string, value: unknown, something: number) => {
    if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
    if (value === something) throw new Error('error')
    return value
}

type Obj = { [P: string]: unknown }

const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }


// main

const evaluate = <
    O extends Obj,
    F extends (key: string, val: unknown) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F) => {
    if (typeof prop !== 'string') throw new Error('error')
    return vfunc(prop, obj[prop])
}

const evaluate_two = <
    O extends Obj,
    F extends (key: string, val: unknown, ...args: unknown[]) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F , ...args: unknown[]) => {
    if (typeof prop !== 'string') throw new Error('error')
    return vfunc(prop, obj[prop], ...args)
}

evaluate(tesstobj, 'one', callback1) // good
evaluate(tesstobj, 'one', callback2) // error as expected



// below expression statements should not fail

evaluate_two(tesstobj, 'one', callback2) // should say missing `min` arg
evaluate_two(tesstobj, 'one', callback2, 1)  
evaluate_two(tesstobj, 'one', callback2, 1, 10)

evaluate_two(tesstobj, 'one', callback3, 1) // should say wrong type number instead of string 

In above snippet I need to able to pass callback1 / callback2 / callback3 to evaluate‘s callback parameter along with its additional arguments. I tried achieve this in evaluate_two but it didn’t work.

Error message

Argument of type '(key: string, value: unknown, min: number, max?: number) => number' is not assignable to parameter of type '(key: string, val: unknown, ...args: unknown[]) => number'.
  Types of parameters 'min' and 'args' are incompatible.
    Type 'unknown' is not assignable to type 'number'.

old playground link

Note: I know this can be achieved with help of union but its not dynamic, which I don’t like.

Edit: Here is updated playground link now with clarifications in response to jcalz’s comment.

2

Answers


  1. You can pass a callback, updated code

    // callbacks
    const callback1 = (key: string, value: unknown) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        return value
    }
    
    const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        if (value < min || value > max) throw new Error('error')
        return value
    }
    
    const callback3 = (key: string, value: unknown, something: number) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        if (value === something) throw new Error('error')
        return value
    }
    
    type Obj = { [P: string]: unknown }
    
    const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }
    
    const evaluate_two = <
        O extends Obj,
        F1 extends () => ReturnType<F1>
    >(obj: O, prop: keyof O, vfunc: F1) => {
        if (typeof prop !== 'string') throw new Error('error')
        return vfunc();
    }
    
    evaluate_two(tesstobj, 'one', () => {
        callback1('one', tesstobj['one']);
    })
    
    evaluate_two(tesstobj, 'one', () => {
        const temp_min = -10;
        const temp_max = 10;
        callback2('one', tesstobj['one'], temp_min, temp_max);
    })
    
    evaluate_two(tesstobj, 'one', () => {
        const something = 100;
        callback3('one', tesstobj['one'], something);
    })
    

    Edit (updated answer as per @bogdanoff’s comment):

    // callbacks
    const callback1 = (key: string, value: unknown) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        return value
    }
    
    const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        if (value < min || value > max) throw new Error('error')
        return value
    }
    
    const callback3 = (key: string, value: unknown, something: number) => {
        if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
        if (value === something) throw new Error('error')
        return value
    }
    
    type Obj = { [P: string]: unknown }
    
    const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }
    
    const evaluate = <
        O extends Obj,
        F extends (key: string, val: unknown) => ReturnType<F> // <--
    >(obj: O, prop: keyof O, vfunc: F) => {
        if (typeof prop !== 'string') throw new Error('error')
        return vfunc(prop, obj[prop])
    }
    
    const evaluate_two = <
        O extends Obj,
        F extends (key: string, val: unknown, ...args: any[]) => ReturnType<F> // <--
    >(obj: O, prop: keyof O, vfunc: F , ...args: any[]) => {
        if (typeof prop !== 'string') throw new Error('error')
        return vfunc(prop, obj[prop], ...args)
    }
    
    evaluate_two(tesstobj, 'one', callback1) 
    evaluate_two(tesstobj, 'one', callback2) 
    evaluate_two(tesstobj, 'one', callback3) 
    

    just change args types from unknown[] to any[]. Hope this helps.

    Login or Signup to reply.
  2. If you want to make sure that args matches the rest of the parameters for F after key: string, val: unknown, then you’ll want another generic type parameter for the type of args:

    const evaluate_two = <
        O extends Obj,
        A extends any[],
        F extends (key: string, val: unknown, ...args: A) => ReturnType<F> 
    >(obj: O, prop: keyof O, vfunc: F, ...args: A) => {
        if (typeof prop !== 'string') throw new Error('error')
        return vfunc(prop, obj[prop], ...args)
    }
    

    Now you get the behavior you expect:

    // const callback2: (key: string, value: unknown, min: number, max?: number) => number
    evaluate_two(tesstobj, 'one', callback2) // error, too few arguments
    evaluate_two(tesstobj, 'one', callback2, 1)
    evaluate_two(tesstobj, 'one', callback2, 1, 10)
    
    // const callback3: (key: string, value: unknown, something: string) => string
    evaluate_two(tesstobj, 'one', callback3, 1) // error, number is not string
    

    Playground link to code

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search