向任何函数添加回调参数

时间:2019-06-04 13:37:03

标签: typescript

我要实现的目标是:给定随机签名的函数f,假设f: (x: string, y: number): boolean,我想创建一个满足以下条件的函数g

  • 签名g: (x: string, y: number, callback?: (b: boolean) => void): void
  • 传递给回调的值是返回值f(x,y)

基本上,我想在不丢失类型信息的情况下回调任何函数。

这是我的看法:

type Callback<T> = (t: T) => void;

function withCallback<F extends(...args: any) => any>(
    f: F,
) {
    return function (...args: any) {
        let callback: Callback<ReturnType<F>> | undefined;
        let params: Array<any> = args;

        if (args.length && typeof (args[args.length - 1]) === 'function') {
            callback = args[args.length - 1];
            params = args.slice(0, args.length - 1);
        }

        const result = f(...params);
        callback(result);
    } as (...args: Parameters<F>) => void; // ???
}

function f(a: string) { return 2 };
const g = withCallback(f);

代码有效:例如

console.log(f('a'));
g('a', console.log);

都将输出相同的内容。但是我不能正确输入。 g具有正确的参数,但可选的回调参数除外,我似乎无法使其合适。更具体地说,我不知道要在标有???的行中放入哪种类型。

TS Playground link

1 个答案:

答案 0 :(得分:2)

我也可以给出解决方案,其中没有什么是可选的(因此f中的函数withCallback(f)没有可选参数,并且调用withCallback(f)(...args, cb)需要cb

问题是,您希望表示将元素V附加到元组类型T end 上的效果。我将其称为Push<T, V>。 TypeScript不支持开箱即用。自rest tuples推出以来,TypeScript 确实支持在元组类型开始上的元素V之前 {1}},但是;我将其称为Cons<V, T>

T

只要元组的元素不是optional,就可以主要根据// Prepend an element V onto the beginning of a tuple T. // Cons<1, [2,3,4]> is [1,2,3,4] type Cons<V, T extends any[]> = ((v: V, ...t: T) => void) extends (( ...r: infer R ) => void) ? R : never; 和映射/条件类型来实现Push<T, V>:< / p>

Cons<V, T>

(问题是// Append an element V onto the end of a tuple T // Push<[1,2,3],4> is [1,2,3,4] // note that this DOES NOT PRESERVE optionality/readonly in tuples. // So unfortunately Push<[1, 2?, 3?], 4> is [1,2|undefined,3|undefined,4] type Push<T extends any[], V> = (Cons<any, Required<T>> extends infer R ? { [K in keyof R]: K extends keyof T ? T[K] : V } : never) extends infer P ? P extends any[] ? P : never : never; 的工作原理是将元素(包括可选元素)向右移动,而Cons最终将它们留在了已移动的位置,而这不是您所需要的也许有一天。将有一种受支持的方法来使Push的行为完全符合期望,但就目前而言,这是我可以合理做到的最好的方法。

因此,带着Push武装起来,这就是我们的前进方式:

Push

让我们看看它是否有效:

type Callback<T> = (t: T) => void;

function withCallback<F extends (...args: any) => any>(f: F) {
  return function(...args: any[]) {
    const params = args.slice(); // as Parameters<F>; <-- this doesn't help, unfortunately
    const callback = params.pop() as Callback<ReturnType<F>>;
    callback(f(...params));
  } as (...args: Push<Parameters<F>, Callback<ReturnType<F>>>) => void;
}

对我很好。好的,希望对您有所帮助。祝你好运!

Link to code


更新:这是通过可选回调前进的一种可能方法,该回调禁止最后一个参数为函数的函数。但是,该函数不能具有可选参数,至少不容易。

由于时间不够,我会让代码说明一切。祝你好运!

function f(a: string) {
  return a.length;
}
const g = withCallback(f);
g("Hello", n => console.log(n - 2)); // okay, console logs 3
g("Goodbye", "oops"); // error!
//           ~~~~~~ <-- "oops" is not assignable to Callback<number>

Link to new code